Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Desenvolvimento eficaz com código legado

Desenvolvimento eficaz com código legado

O código legado se faz presente na vida de todo engenheiro de software. Saber como lidar com ele é um grande ganho para o negócio, aprenda formas de como manipula-lo e por fim vencê-lo.

Avatar for Gabriel Brasileiro

Gabriel Brasileiro

October 18, 2023

More Decks by Gabriel Brasileiro

Other Decks in Technology

Transcript

  1. Apresentação Gabriel Brasileiro - Staff Software Engineer na Porto; -

    Bacharel em Sistemas de Informação; - Idealizador de libs open source; - Entusiasta e estudioso do mercado de capitais; - Projetos e Start-ups.
  2. A mecânica das alterações Estrutura Nova funcionalidade Funcionalidade Uso de

    recursos Inclusão de recurso Correções Refatoração Otimização
  3. Propagação de efeitos /** * O estado e as variáveis

    internas * desse objeto podem estar sendo alteradas! * * E essas alterações podem se propagar no * consumo do objeto. */ private CustomData customData; public Effect(CustomData customData) { this.customData = customData; }
  4. Propagação de efeitos /** * O controle aberto de um

    estado estático * representa um alto risco de volatilidade * no consumidor da variável. */ public static CustomData customData = new CustomData("start"); public void changeState() { customData = new CustomData("execute"); }
  5. Pontos de intercepção de fixação class Bank { withdraw(value: number)

    { const balance = this.getBalance() if (value <= balance) { console.log("Sucesso no saque") } else { console.log("Erro no saque") } } private getBalance() { // Mundo externo que chama uma API para retorno do saldo. return 1000.0 } } const bank= new Bank() bank.withdraw(500)
  6. Os elos da herança e do polimorfismo 1. Em casos

    de métodos privados ou final, transforme-os em métodos de pacote e permita o controle da herança e do polimorfismo
  7. public class MessageDispatcherLegacy { private static final String colon =

    ":"; private final String key = "identifier"; final public void processMessage(String contentMessage) { String allMessage = getKey() + colon + contentMessage; String transformed = allMessage.toUpperCase(); dispatchMessage(transformed); } protected void dispatchMessage(final String message) { System.out.println(message); // Local que realiza conexão com o mundo exterior. } protected String getKey() { return key; } // Estruturas que podem existir em classes monstruosas. } Os elos da herança e do polimorfismo
  8. public class MessageDispatcherFake extends MessageDispatcherLegacy { private final String customKey;

    private String lastMessage; public MessageDispatcherFake(String customKey) { this.customKey = customKey; } @Override final protected void dispatchMessage(String message) { this.lastMessage = message; } @Override final protected String getKey() { return customKey; } protected String getLastMessage() { return lastMessage; } } Os elos da herança e do polimorfismo
  9. class MessageDispatcherLegacy { private static readonly colon = ":"; private

    readonly key = "identifier"; public processMessage(contentMessage: string): void { const allMessage = this.getKey() + MessageDispatcherLegacy.colon + contentMessage const transformed = allMessage.toUpperCase(); this.dispatchMessage(transformed); } protected dispatchMessage(message: string): void { console.log(message); // Local de conexão com o mundo exterior. } protected getKey(): string { return this.key } } Os elos da herança e do polimorfismo
  10. class MessageDispatcherFake extends MessageDispatcherLegacy { private readonly customKey: string private

    lastMessage: string | undefined constructor(customKey: string) { super(); this.customKey = customKey } protected dispatchMessage(message: string): void { this.lastMessage = message } protected getKey(): string { this.customKey } public getLastMessage(): string | undefined { return this.lastMessage } } Os elos da herança e do polimorfismo
  11. 2. Realize o teste na parte específica da mudança Os

    elos da herança e do polimorfismo
  12. public static void main(String[] args) { // Given String message

    = "Atualizar as bases"; String key = "teste"; String expectedMessage = "TESTE:ATUALIZAR AS BASES"; MessageDispatcherFake fakeDispatcher = new MessageDispatcherFake(key); // When fakeDispatcher.processMessage(message); // Then String lastMessage = fakeDispatcher.getLastMessage(); boolean isEquals = lastMessage.equals(expectedMessage); System.out.println("Resultado: " + isEquals); } Os elos da herança e do polimorfismo
  13. function main(): void { // Given const message = "Atualizar

    as bases"; const key = "teste"; const expectedMessage = "TESTE:ATUALIZAR AS BASES"; const fakeDispatcher = new MessageDispatcherFake(key); // When fakeDispatcher.processMessage(message); // Then const lastMessage: string | undefined = fakeDispatcher.getLastMessage(); const isEquals: boolean = lastMessage === expectedMessage; console.log("Resultado: " + isEquals); } Os elos da herança e do polimorfismo
  14. 3. Verifique se os efeitos não se propagam além do

    seu mapeamento Os elos da herança e do polimorfismo
  15. Princípio de Substituição de Liskov public class Rectangle { public

    Rectangle(int x, int y, int width, int height) { ... } public void setWidth(int width) { ... } public void setHeight(int height) { ... } public int getArea() { ... } } public class Square extends Rectangle { public Square(int x, int y, int width) { ... } } public static void main(String[] args) { Rectangle rectangle = new Square(...); rectangle.setHeight(3); rectangle.setHeight(4); }
  16. Princípio de Substituição de Liskov Observe se precisa de um

    cast para acessar um método ou propriedade
  17. Princípio de Substituição de Liskov public static void main(String[] args)

    { Vehicle vehicle = new Car(); Car car = (Car) vehicle; car.drive(); }
  18. class IntCalculator { constructor() { this.tax = 10 } getTotal(value)

    { return value * this.tax } } class FloatCalculator { constructor() { this.tax = 10.0 } getTotal(value) { const total = value * this.tax return `${total}` } } Os perigos do duck typing function calculate(calculator) { const total = calculator.getTotal(10) const addOne = total + 1 console.log(addOne) } const intCalculator = new IntCalculator() const floatCalculator = new FloatCalculator() calculate(intCalculator) calculate(floatCalculator)
  19. class Dog: def speak(self): print("O cachorro fala Woof!") class Cat:

    def speak(self): print("O gato fala Meow!") def make_animal_speak(animal): animal.speak() dog = Dog() cat = Cat() make_animal_speak(dog) # Saída: O cachorro fala Woof! make_animal_speak(cat) # Saída: O gato fala Meow! Os perigos do duck typing
  20. Strategy fun main() { val hardWork = HardWorkClazz() val processInfo

    = ProcessInfo() processInfo.startNewScope(hardWork.getStrategy()) } class ProcessInfo { fun startNewScope( legacyStrategy: LegacyStrategy ) { val immutableData = legacyStrategy.getData() println(immutableData.name) } } data class NewInfo( val name: String ) fun interface LegacyStrategy { fun getData(): NewInfo } class HardWorkClazz { private var legacyName = "" fun getStrategy(): LegacyStrategy { return LegacyStrategy { NewInfo(legacyName) } } /** * Conjunto de funções de * manipulação com alto risco. * ... */ }
  21. class ProcessInfo { startNewScope(legacyStrategy: LegacyStrategy) { const name = legacyStrategy.getName();

    console.log(name); } } const hardWork = new HardWorkClazz(); const processInfo = new ProcessInfo(); processInfo.startNewScope(hardWork.getStrategy()); Strategy class NewInfo { constructor(readonly name: String) {} } interface LegacyStrategy { getName(): NewInfo; } class HardWorkClazz { private legacyName = 'legacy'; getStrategy(): LegacyStrategy { return { getName: () => { return new NewInfo(this.legacyName) } } } }
  22. Strategy - Teste data class NewInfo( val name: String )

    fun interface LegacyStrategy { fun getData(): NewInfo } interface FinishExecution { fun terminate(message: String) } class FakeFinish : FinishExecution { private var lastMessage = String() override fun terminate(message: String) { lastMessage = message } fun getLastMessage(): String = lastMessage } class ProcessInfo( private val finishExecution: FinishExecution ) { fun startNewScope( legacyStrategy: LegacyStrategy ) { val immutableData = legacyStrategy.getData() val name = immutableData.name finishExecution.terminate(name) } }
  23. Strategy - Teste fun main() { val expected = "Mário"

    val testStrategy = LegacyStrategy { NewInfo(name = "Mário") } val fakeFinishExecution = FakeFinish() val processInfo = ProcessInfo(fakeFinishExecution) processInfo.startNewScope(testStrategy) val result = fakeFinishExecution.getLastMessage() println(result == expected) // Saída: True }
  24. Strategy - Dicas Durante a fase de desenvolvimento também é

    possível usar lambdas para a criação dos blocos de estratégia.
  25. data class NewContext( val param: String ) class LegacyContext {

    var state = "runtime" /** Estrutura não compreensível */ } // Interface de recepção interface TerminateState { fun sendMessage(message: String) } // Interface de controle interface ProcessState { fun createState() } // Interface de isolamento interface ExecutionScope { // Criação do contexto funcional fun onScope(block: (NewContext) -> Unit) } Functional Replacement
  26. class TerminateEnvironment : TerminateState { override fun sendMessage(message: String) {

    println("Processo terminado: $message") } } class TransformEnvironment : ExecutionScope { // Contexto não interpretável isolado private val legacyContext = LegacyContext() // Contexto transformado e emitido override fun onScope(block: (NewContext) -> Unit) { val state = legacyContext.state val newContext = NewContext(state) block(newContext) } } class ProcessStateEnvironment( private val executionScope: ExecutionScope, private val terminateState: TerminateState ) : ProcessState { override fun createState() { executionScope.onScope { newContext: NewContext -> // Condicionais e novas estruturas terminateState.sendMessage(newContext.param.uppercase()) } } } Functional Replacement
  27. fun main() { val transformEnvironment: ExecutionScope = TransformEnvironment() val terminateEnvironment:

    TerminateState = TerminateEnvironment() val processState: ProcessState = ProcessStateEnvironment(transformEnvironment, terminateEnvironment) processState.createState() // Saída: Processo terminado: RUNTIME } Functional Replacement
  28. class TransformEnvironmentFake( private val newContext: NewContext ) : ExecutionScope {

    override fun onScope(block: (NewContext) -> Unit) { block(newContext) } } class TerminateEnvironmentFake : TerminateState { private var lastMessage = String() override fun sendMessage(message: String) { lastMessage = message } fun getLastMessage(): String = lastMessage } Functional Replacement - Teste
  29. fun main() { val expected = "GIVA" val newContext =

    NewContext("Giva") val fakeEnvironment = TransformEnvironmentFake(newContext) val terminateEnvironmentFake = TerminateEnvironmentFake() val processState: ProcessState = ProcessStateEnvironment(fakeEnvironment, terminateEnvironmentFake) processState.createState() val result = terminateEnvironmentFake.getLastMessage() println(expected == result) // Saída: True } Functional Replacement - Teste
  30. data class ImpossibleData( var status: String, var state: String )

    class ImpossibleObject { private var impossibleData = ImpossibleData( state = "execution", status = "good" ) fun getCurrentState(): ImpossibleData { return impossibleData } // Operações extras e "impossíveis de entender". } Humble Object
  31. data class Consumer( val state: String ) interface AdapterConsumer {

    fun requireConsumer(): Consumer } class AdapterScopeConsumer( private val impossibleObject: ImpossibleObject ): AdapterConsumer { override fun requireConsumer(): Consumer { val impossibleData =impossibleObject.getCurrentState() return Consumer(impossibleData.state) } } Humble Object
  32. fun main() { val impossibleObject = ImpossibleObject() val adapterConsumer: AdapterConsumer

    = AdapterScopeConsumer(impossibleObject) newFlowOperation(adapterConsumer) } fun newFlowOperation(adapterConsumer: AdapterConsumer) { val consumer = adapterConsumer.requireConsumer() val state = consumer.state println("Estado imutável recuperado: $state") // Saída: Excecution } Humble Object
  33. Switch de execução Escolha o seu contrato de retorno; Defina

    uma classe base ou interface para extração dos dados; Retorne, valide e destrua o código legado!
  34. Switch de execução class ContextLegacy { private var name =

    "mário" private var age = 58 fun getData(): ContextData { return ContextData(name, age) } /** Objeto monstruoso com dados diversos */ } class ContextNew { fun getData(): ContextData { return ContextData("gabriel", 24) } } class ContextToggle { fun isEnabled(): Boolean = true } interface ContextDecider { fun getData(): ContextData } class ContextScopeDecider( private val contextLegacy: ContextLegacy, private val contextNew: ContextNew, private val contextToggle: ContextToggle ) : ContextDecider { override fun getData(): ContextData { return if (contextToggle.isEnabled()) { contextNew.getData() } else { contextLegacy.getData() } } }
  35. O uso de interfaces no retorno do switch assim como

    o Strategy são bem-vindas! Switch de execução
  36. Use seu framework de testes Após orientar seu código a

    interfaces a estrutura do seu teste fica simples!
  37. Cada pequena mudança e melhoria é um investimento para o

    futuro no médio e no longo prazo do projeto
  38. Fontes - Trabalho eficaz com código legado - Michael C.

    Feathers; - Arquitetura Limpa - Robert C. Martin; - Dive Into Design Patterns - Alexander Shvets; - Functional Programming: https://en.wikipedia.org/wiki/Functional_programming - Pure Functions: https://en.wikipedia.org/wiki/Pure_function - Liskov Substitution Principle: https://en.wikipedia.org/wiki/Liskov_substitution_principle - Given, when, then: https://martinfowler.com/bliki/GivenWhenThen.html - Humble Object: https://martinfowler.com/bliki/HumbleObject.html - SOLID: https://en.wikipedia.org/wiki/SOLID - GWT: https://martinfowler.com/bliki/GivenWhenThen.html - Patterns: https://refactoring.guru/design-patterns