Olá pessoal. Tudo certo?!
Nesse post, mostro mais um exemplo de refactoring. Outra vez, utilizo, como exemplo, um código do projeto FluentCodeMetrics.
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?
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:
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.