Elemar DEV

Tecnologia e desenvolvimento .net

Outro exemplo de Refactoring

Olá pessoal. Tudo certo?!

Nesse post, mostro mais um exemplo de refactoring. Outra vez, utilizo, como exemplo, um código do projeto FluentCodeMetrics.

Nosso ponto de partida

Para começar, vejamos o código que desejamos refatorar. Veja:

using System.Linq;
using System.Reflection;

using FluentCodeMetrics.Core.Cecil;
using Mono.Cecil.Cil;

namespace FluentCodeMetrics.Core
{
    public class Cc : CodeMetric
    {
        private Cc(int value)
        {
            this.value = value;
        }

        private readonly int value;
        public override int Value
        {
            get { return value; }
        }

        static OpCode[] ccBranchOpCodes = new[]
            {
                OpCodes.Beq, OpCodes.Beq_S, OpCodes.Bge, OpCodes.Bge_S,
                OpCodes.Bge_Un, OpCodes.Bge_Un_S, OpCodes.Bgt, OpCodes.Bgt_S,
                OpCodes.Bgt_Un, OpCodes.Bgt_Un_S, OpCodes.Ble, OpCodes.Ble_S,
                OpCodes.Ble_Un, OpCodes.Ble_Un_S, OpCodes.Blt, OpCodes.Blt_S,
                OpCodes.Blt_Un, OpCodes.Blt_Un_S, OpCodes.Bne_Un, OpCodes.Bne_Un_S,
                OpCodes.Brfalse, OpCodes.Brfalse_S,
                OpCodes.Brtrue, OpCodes.Brtrue_S
            };

        // TODO: Support to overloaded methods
        public static Cc For(MethodInfo method)
        {
            var type = method.DeclaringType;
            var assembly = AssemblyCache.Load(type.Assembly.GetName().Name);

            var typeDef = assembly.MainModule.Types
                .First(t => t.FullName == type.FullName);

            var methodDef = typeDef.Methods
                .First(m => m.Name == method.Name);

            var ccBranchInstructions =
                from instruction in methodDef.Body.Instructions
                where ccBranchOpCodes.Contains(instruction.OpCode)
                select instruction;

            var ccSwitchInstructions =
                from instruction in methodDef.Body.Instructions
                where instruction.OpCode == OpCodes.Switch
                from operand in (Instruction []) instruction.Operand
                select operand;

            var ccRetInstructions =
                from instruction in methodDef.Body.Instructions
                where instruction.OpCode == OpCodes.Ret
                select instruction;


            return new Cc(
                ccBranchInstructions.Count() +
                ccSwitchInstructions.Count() +
                ccRetInstructions.Count()
            );
        }
    }
}

Este código deve calcular corretamente a Complexidade Ciclomática de métodos em diferentes cenários (ainda não está 100%).

O que há de ruim nesse código?

  • o método estático For está fazendo muita coisa;
  • a lógica para recuperar a “definição” do método (Cecil) não está diretamente relacionada com a “responsabilidade” do método;
  • a lista de instruções é percorrida repetidas vezes para verificação de diferentes evidências de Complexidade Ciclomática.

Refactoring

Para começar, vamos extrair o código responsável pela interface com Cecil (contribuição do Guilherme Oenning). Veja:

using System;
using System.Linq;
using System.Reflection;

using Mono.Cecil;

namespace FluentCodeMetrics.Core.Cecil
{
    public static class CecilExtensions
    {
        public static Type ToType(this TypeReference reference)
        {
            TypeDefinition definition = reference.Resolve();
            return Type.GetType(
                string.Format("{0}, {1}", definition.FullName, definition.Module.Assembly.FullName)
                );
        }

        public static TypeDefinition ToDefiniton(this Type type)
        {
            var assembly = AssemblyCache.Load(type.Assembly.GetName().Name);
            return assembly.MainModule.Types.FirstOrDefault(t => t.FullName == type.FullName);
        }

        public static MethodDefinition ToDefinition(this MethodInfo method)
        {
            return method.DeclaringType.ToDefiniton().Methods
                .First(m => m.Name == method.Name);
        }
    }
}

Isolar esses novos métodos proporciona:

  • Reutilização da lógica;
  • Implementação facilitada de testes.

Agora, vejamos como ficou a implementação da classe Cc.

using System;

using System.Linq;
using System.Reflection;

using Mono.Cecil.Cil;
using FluentCodeMetrics.Core.Cecil;

namespace FluentCodeMetrics.Core
{
    public sealed class Cc : CodeMetric
    {
        private Cc(int value)
        {
            this.value = value;
        }

        private readonly int value;
        public override int Value
        {
            get { return value; }
        }

        static OpCode[] ccBranchOpCodes = new[]
            {
                OpCodes.Beq, OpCodes.Beq_S, OpCodes.Bge, OpCodes.Bge_S,
                OpCodes.Bge_Un, OpCodes.Bge_Un_S, OpCodes.Bgt, OpCodes.Bgt_S,
                OpCodes.Bgt_Un, OpCodes.Bgt_Un_S, OpCodes.Ble, OpCodes.Ble_S,
                OpCodes.Ble_Un, OpCodes.Ble_Un_S, OpCodes.Blt, OpCodes.Blt_S,
                OpCodes.Blt_Un, OpCodes.Blt_Un_S, OpCodes.Bne_Un, OpCodes.Bne_Un_S,
                OpCodes.Brfalse, OpCodes.Brfalse_S,
                OpCodes.Brtrue, OpCodes.Brtrue_S
            };

        // TODO: Support to overloaded methods
        public static Cc For(MethodInfo method)
        {
            var ccInstructions =
                from instruction in method.ToDefinition().Body.Instructions
                where (
                    ccBranchOpCodes.Contains(instruction.OpCode) ||
                    instruction.OpCode == OpCodes.Switch ||
                    instruction.OpCode == OpCodes.Ret
                )
                select instruction;

            Func<Instruction, int> ccWeight = instruction => 
                instruction.OpCode == OpCodes.Switch
                ? ((Instruction[]) instruction.Operand).Length
                : 1;
            
            var value = ccInstructions.Sum(ccWeight);

            return new Cc(value);
        }
    }
}

Perceba como o método For ficou menor. Retiramos a “lógica do Cecil”. Além disso, agora percorremos a lista de instruções apenas uma vez.

Bacana, não?!

Era isso.

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

WordPress.com Logo

Você está comentando usando sua conta WordPress.com. Sair / Mudar )

Imagem do Twitter

Você está comentando usando sua conta Twitter. Sair / Mudar )

Foto do Facebook

Você está comentando usando sua conta Facebook. Sair / Mudar )

Conectando a %s

Informação

Publicado às 26/05/2012 por em Post e marcado .

Estatísticas

  • 427,317 hits
%d bloggers like this: