UNIVERSIDADE TECNOLÓGICA FEDERAL DO PARANÁ CAMPUS PONTA GROSSA CURSO SUPERIOR EM BACHARELADO EM CIÊNCIA DA COMPUTAÇÃO Grupo de Pesquisa em Sistemas de Informação - Linhas de Pesquisa em Engenharia de Software - GPSI Projeto: Refatoração do FrameMK Elaborado por: Luma Alves Lopes [email protected] Víctor Pedroso Ambiel Barros [email protected] Relatório do GPSI Relatório referente ao estudo sobre a técnica de refatoração para framework. Realizado no período de 30 de Agosto de 2013 a 20 de Setembro de 2013. Os assuntos abordados neste relatório foram baseados em Opdyke [1]. Preservar o comportamento durante a refatoração 1. Propriedades do programa de preservação e comportamento Depois que uma refatoração é aplicada, o programa deve estar sintaticamente correto, pois não há mudanças no funcionamento de suas operações. Por exemplo, na subclasse ao redefinir uma função declarada na superclasse, é necessário manter o comportamento interno das duas classes. O compilador pode detectar este erro, logo, uma solução é salvar a versão atual do programa antes de cada refatoração ser aplicada, pois se a refatoração levar a um erro, basta voltar à última versão salva do programa. O artigo cita dois grandes problemas para essa abordagem. O primeiro é que a abordagem pode ser lenta, tendo em vista operações que necessitam de refatorações de alto nível que envolve refatorações primitivas. A segunda, que é mais importante ainda que a primeira, é que existem alguns erros que podem alterar o comportamento do programa, mas não são identificados pelo compilador, e desta forma, passam despercebidos. Na Figura 1, a função F1 é um membro protegido na classe Super, enquanto F2 é definida na classe Sub1, umas das suas subclasses. O argumento e retorno de F2 são os mesmos de F1. Na classe Sub1, a função F3 inclui uma chamada a função F1, que é herdada da superclasse. A refatoração é aplicada na classe Sub1, para mudar o nome da função F2 para F1. No entanto, o comportamento do programa provavelmente também muda, o que não deveria acontecer. 1 UNIVERSIDADE TECNOLÓGICA FEDERAL DO PARANÁ CAMPUS PONTA GROSSA CURSO SUPERIOR EM BACHARELADO EM CIÊNCIA DA COMPUTAÇÃO Grupo de Pesquisa em Sistemas de Informação - Linhas de Pesquisa em Engenharia de Software - GPSI Projeto: Refatoração do FrameMK Elaborado por: Luma Alves Lopes [email protected] Víctor Pedroso Ambiel Barros [email protected] Figura 1 - Renomeação errônea dos membros da função F2 - Erroneous Renaming Of Member Function. Fonte: Opkney [1, p. 38] A função F3 é definida na classe local, não é uma função previamente herdada da superclasse, o que seria o correto. Se as duas funções se comportam de maneira diferentes, o comportamento da função F3 teria mudado e o compilador não detectaria esse erro. Durante a criação de protótipos de pesquisa, um conjunto particular de propriedades sintáticas e semânticas de programas foram encontradas para ser facilmente violadas se as verificações não forem feitas antes de um programa ser refatorado. Estas propriedades referem-se a herança, escopo, tipo de compatibilidade e equivalência semântica. As propriedades citadas no artigo são: Superclasse única: depois da refatoração, uma classe sempre tem no máximo uma superclasse e sua superclasse também não deve ser uma de suas subclasses. Nome de classes distintas: depois da refatoração, cada classe deve ter um nome único. 2 UNIVERSIDADE TECNOLÓGICA FEDERAL DO PARANÁ CAMPUS PONTA GROSSA CURSO SUPERIOR EM BACHARELADO EM CIÊNCIA DA COMPUTAÇÃO Grupo de Pesquisa em Sistemas de Informação - Linhas de Pesquisa em Engenharia de Software - GPSI Projeto: Refatoração do FrameMK Elaborado por: Luma Alves Lopes [email protected] Víctor Pedroso Ambiel Barros [email protected] Os nomes distintos: depois da refatoração, todas as variáveis-membro e funções dentro de uma classe tem nomes distintos. No entanto, não é permitido que uma função-membro de uma superclasse seja substituída em uma subclasse. Variáveis-membro herdadas: a variável membro herdada de uma superclasse não é redefinida em qualquer uma de suas subclasses. Assinaturas compatíveis nos estados de redefinição da função: depois da refatoração, se um membro da função definida em uma superclasse é redefinida em uma subclasse, todos os atributos (exceto o corpo da função) das duas funções devem ser compatíveis. Atribuições de tipos de seguro: depois de uma refatoração, o tipo de cada expressão atribuída a variável deve ser uma instância do tipo definido na variável. 2. Refatoração por especialização: subclasses e simplificação de condicionais Pode-se melhorar uma classe grande e complexa dividindo-a em classes menores. Isso ocorre geralmente quando uma classe complexa representa uma abstração geral e contempla vários casos concretos que podem ser especializados. A decomposição da classe pode ser realizada com base na análise do conjunto de flags, tags e das suas instruções condicionais. Para cada condição, uma subclasse é criada satisfazendo a sua abstração. Especializar uma classe e simplificar condicionais envolve várias etapas: 1 – Escolher uma condicional cuja condição sugere a criação de subclasse. A escolha da condicional pode ser com destaque aquelas que aparecem com mais frequência. 2 – Para cada condição criar uma subclasse com uma classe invariante. 3 – Copiar a condicional para cada subclasse, e nas subclasses simplificá-las com base na invariável que é verdade para a subclasse. 3 UNIVERSIDADE TECNOLÓGICA FEDERAL DO PARANÁ CAMPUS PONTA GROSSA CURSO SUPERIOR EM BACHARELADO EM CIÊNCIA DA COMPUTAÇÃO Grupo de Pesquisa em Sistemas de Informação - Linhas de Pesquisa em Engenharia de Software - GPSI Projeto: Refatoração do FrameMK Elaborado por: Luma Alves Lopes [email protected] Víctor Pedroso Ambiel Barros [email protected] 4 – Especializar as expressões que criam as instâncias da SuperClasse, substituindo-a por uma expressão que crie a instância de uma subclasse. 2.1. Classes Invariantes (constantes) Essa classe é um predicado cujas variáveis livres são as variáveis-membro da classe. Ela expressa restrições de consistências gerais que se aplicam a todas as instâncias de uma classe. Para que a classe esteja correta se deve: Criar um procedimento para a classe produzir um estado que satisfaça a classe invariante. A classe deve permanecer invariável até o restante do tempo de vida de cada instância da classe. Otimizações locais se caracterizam como sendo alterações aplicadas aos segmentos de código. Como por exemplo, avaliar expressões constantes de antecedência e eliminar instruções inúteis. Otimizações globais lidam com o fluxo de controle entre os blocos de base. As refatorações que simplificam condicionais podem ser pensadas como uma espécie de otimização global. Para aperfeiçoar um programa ou procedimento, torna-se necessário determinar os conjuntos de variáveis lidos e atualizados por ele. Entretanto, isso deve ser analisado para que a otimização seja segura, sendo a análise dos efeitos de uma ligação dos dados denominado Interprocedural da análise de fluxo. A Interprocedural análise de Fluxo de dados é um procedimento ou subprograma que pode ser representado como um grafo de fluxo de dados de blocos de base, onde as arestas direcionadas representam o fluxo de controle dentro do procedimento, cada nó representa um procedimento e arcos direcionados às chamadas entre os procedimentos. 4 UNIVERSIDADE TECNOLÓGICA FEDERAL DO PARANÁ CAMPUS PONTA GROSSA CURSO SUPERIOR EM BACHARELADO EM CIÊNCIA DA COMPUTAÇÃO Grupo de Pesquisa em Sistemas de Informação - Linhas de Pesquisa em Engenharia de Software - GPSI Projeto: Refatoração do FrameMK Elaborado por: Luma Alves Lopes [email protected] Víctor Pedroso Ambiel Barros [email protected] Alguns problemas de fluxo de dados podem ser resolvidos utilizando a análise de fluxo para frente, seguindo a direção do fluxo de um programa, outros, entretanto, são resolvidos analisando o fluxo no sentido oposto. Poderá haver casos onde um predicado é uma classe invariante, mas não pode ser verificado por meio da análise de fluxo de dados. O que torna esses predicados difíceis de validar é que cada cláusula garante um valor de uma variável de membro e essa variável pode mudar de valor ao longo do tempo. Figura 2 – Exemplo de como uma classe invariante pode ser usado para simplificar uma condicional. Fonte: Opkney [1, p. 39] Neste exemplo, o predicado atribuído como classe invariante do CondJumpFlowNode é final_statement==conditional_jump especificado em termos de 5 UNIVERSIDADE TECNOLÓGICA FEDERAL DO PARANÁ CAMPUS PONTA GROSSA CURSO SUPERIOR EM BACHARELADO EM CIÊNCIA DA COMPUTAÇÃO Grupo de Pesquisa em Sistemas de Informação - Linhas de Pesquisa em Engenharia de Software - GPSI Projeto: Refatoração do FrameMK Elaborado por: Luma Alves Lopes [email protected] Víctor Pedroso Ambiel Barros [email protected] um valor fixo. Esta invariante pode ser utilizada para simplificar a expressão condicional na função otimizar, removendo o teste condicional. 2.2. Criar subclasses e atribuir invariantes O primeiro passo é criar novas subclasses e atribuir invariantes de classe. Os argumentos para essa refatoração são: A classe sendo especializada Valores das variáveis membros são enumerados para utilizá-las na criação de subclasses. Para cada valor enumerado: 1 - Chamar uma classe vazia de refatoração para criar uma subclasse, com o nome correspondente ao valor enumerado. 2 - Chamar a criação da função membro para adicionar um construtor para a subclasse. O construtor irá estabelecer condições iniciais implicadas pela classe invariante. 3 - Atribuir um predicado para a classe como a classe invariante. Para aplicar essa refatoração, nenhum dos nomes dos valores enumerados podem coincidir com os existentes e nem com o nome das funções- membro da superclasse. 2.3. Usar uma classe invariável para simplificar uma condicional O algoritmo consiste em: Reduzir verdadeiro ou falso para cada condição de uma condicional com base em uma classe invariante. Eliminar código não utilizado e executar outras simplificações. 2.4. Migrar e Simplificar condicionais 6 UNIVERSIDADE TECNOLÓGICA FEDERAL DO PARANÁ CAMPUS PONTA GROSSA CURSO SUPERIOR EM BACHARELADO EM CIÊNCIA DA COMPUTAÇÃO Grupo de Pesquisa em Sistemas de Informação - Linhas de Pesquisa em Engenharia de Software - GPSI Projeto: Refatoração do FrameMK Elaborado por: Luma Alves Lopes [email protected] Víctor Pedroso Ambiel Barros [email protected] As declarações são separadas em novas funções, que são copiadas e simplificadas para as subclasses. Separa a condicional numa função independente, copiando-a para as subclasses e simplificando-as com base na classe invariante, simplificando a instrução condicional. Após verificar a condição, a refatoração consiste em: 1 - Chamar o segmento do código convertido para separar a condicional em uma função separada, atribuindo um nome exclusivo. 2 - Construir o conjunto de todas as funções-membro e variáveis referenciadas pela condicional. Para cada membro, mudar o controle de acesso para protegido. 3 – Chamar a criação da função membro para copiar a nova função para cada uma das novas subclasses. 4 - Para uma instrução condicional de uma classe invariante pode ser determinado: (A) simplificação da instrução condicional. (B) Eliminação de todos os ramos onde o teste for falso (sem efeitos) contra a classe invariante. (C) Converter qualquer ramo onde o teste for falso (as com efeitos colateriais) para a correção da declaração. 2.5. Usar uma classe invariável para especializar expressões que criam instâncias Depois da definição das subclasses, o programa pode, às vezes, ser melhorado especializando as expressões que criam as instâncias da superclasse, ou seja, substituir uma expressão que cria uma instância da superclasse por uma que crie uma instância da subclasse. Isso não é feito com segurança, a menos que seja determinado que nenhuma instância criada pela expressão viola o invariante da subclasse, o que envolve encontrar onde as instâncias criadas por esta expressão são inicialmente atribuídas e encontrar os [S1] Comentário: não compreendi lugares onde a variável pode ser utilizada. Se a variável é passada por referência em uma chamada de função, toda a sua cadeia de uso também deve ser construída. Para determinar que a instância pode ser 7 UNIVERSIDADE TECNOLÓGICA FEDERAL DO PARANÁ CAMPUS PONTA GROSSA CURSO SUPERIOR EM BACHARELADO EM CIÊNCIA DA COMPUTAÇÃO Grupo de Pesquisa em Sistemas de Informação - Linhas de Pesquisa em Engenharia de Software - GPSI Projeto: Refatoração do FrameMK Elaborado por: Luma Alves Lopes [email protected] Víctor Pedroso Ambiel Barros [email protected] especializada, nenhuma operação sobre estas variáveis podem violar o invariante definido na subclasse. 2.6. Especializar Expressões que criam instâncias Os argumentos são: Expressão que cria uma instância da superclasse. Substituir a expressão que cria uma instância da nova subclasse. Em seguida: 1 - Substituir a expressão com uma expressão equivalente que cria instâncias da subclasse. Para executar essa refatoração a pré-condição é que as instâncias criadas pela expressão não podem violar a subclasse invariável. 2.7. Discussão Se houver alguma variável-membro definida na superclasse que são somente referenciadas em um subconjunto das subclasses, então elas podem ser migradas para baixo nas subclasses. As refatorações citadas neste relatório consideram especializações com base na condição de uma instrução condicional e classes invariantes. Há ainda casos, em que o estado de uma instância pode mudar violando as invariantes definidas nas subclasses. Referências [1] OPDYKE, William F. Refactoring Object-Oriented Frameworks. Urbana: Illinois, 1992. 8