Olá pessoal. Tudo certo?!
Muito contente com a reação da comunidade a esse novo projeto. Muitas mensagens de incentivo, muitas referências de conteúdo, pull requests e refinamentos para a especificação.
Se você chegou agora, talvez queira ver o primeiro post dessa nova série. Tudo é muito novo, ainda. Não há um modelo de objetos nem um modelo fixo para os testes. Por isso mesmo, há muito espaço para colaboração.
Lembre-se: todo código-fonte está no GitHub.
O Israel Aece, grande MVP e autor de um dos blogs mais bacanas que eu já tive o prazer de ler, observou alguns esquecimentos importantes em minha especificação: meta-atributos e eventos. Agora, essa é a especificação atualizada:
#language: pt-BR
Funcionalidade: Calcular Acoplamento Eferente (Ce)
Acomplamento Eferente é uma métrica que indica a quantidade
de referências de um tipo para outros.
Entram na contagem: classe base, interfaces implementadas,
tipos dos variáveis locais, atributos e das propriedades,
tipos dos parâmetros em métodos e construtores, exceptions,
eventos e atributos (obrigado @IsraelAece).
Como o objetivo é identificar a "complexidade" de um tipo,
considero também as referências "herdadas".
Esquema do Cenário: Calcular Acoplamento Eferente para um tipo
Dado que tenho um <tipo>
Quando inspeciono seu acoplamento eferente
Então obtenho <ce>
#
# todos os tipos tem object, boolean, string, int32, type no mínimo
#
# além disso todos os tipos tem os seguintes atributos:
#
# System.Runtime.TargetedPatchingOptOutAttribute,
# System.Security.SecuritySafeCriticalAttribute,
# System.Runtime.ConstrainedExecution.ReliabilityContractAttribute,
#
# propriedades automáticas geram "atributos de classe" marcados com:
#
# System.Runtime.CompilerServices.CompilerGeneratedAttribute
#
Exemplos:
| tipo | ce |
| Samples.EmptyClass | 8 |
| Samples.SingleArgCtor | 9 |
| Samples.SingleArgVoidMethod | 9 |
| Samples.FeeMethod | 9 |
| Samples.DateTimeArgDateTimeMethod | 9 |
| Samples.SingleProperty | 10 |
| Samples.SingleField | 9 |
| Samples.ExceptionRaiser | 9 |
| Samples.SingleNonAutoProperty | 9 |
| Samples.SingleEvent | 9 |
| Samples.Attributes | 12 |
Para cumprir as novas especificações, criei mais dois tipos “exemplo”. Veja:
public class SingleEvent
{
public EventHandler Event;
}
[Serializable]
public class Attributes
{
[NonSerialized] public int FooField;
[FooAttribute]
public void FooMethod([FooAttribute2] int foo) {}
}
public class FooAttribute : Attribute
{
}
public class FooAttribute2 : Attribute
{}
Como você pode ver, na própria especificação, a verificação de atributos revela algum acoplamento “oculto” para atributos emitidos pelo compilador. Aliás, no futuro isso implicará na implementação de algum tipo de filtro para deixar nas métricas apenas aquilo que interessa.
Graças ao SpecFlow, não tive grande trabalho com os testes de aceitação. Mas, com o aumento dos “acoplamentos comuns”, comecei a perceber uma duplicação insistente de código nos testes de unidade. Eis a minha solução:
using System;
using System.Linq;
using System.Collections.Generic;
using NUnit.Framework;
using SharpTestsEx;
using FluentCodeMetrics.Core;
using Samples;
namespace FluentCodeMetrics.Tests
{
[TestFixture]
// ReSharper disable InconsistentNaming
public class CeExtensionsTests
{
private readonly List<Type> common = new List<Type>()
{
typeof (System.Runtime.TargetedPatchingOptOutAttribute),
typeof (System.Security.SecuritySafeCriticalAttribute),
typeof (System.Runtime.ConstrainedExecution.ReliabilityContractAttribute),
typeof (object),
typeof (string),
typeof (bool),
typeof (int),
typeof (Type)
};
[Test]
public void GetReferencedTypes_EmptyClass()
{
typeof(EmptyClass).GetReferencedTypes()
.Should().Have.SameValuesAs(
common
);
}
[Test]
public void GetReferencedTypes_SingleField()
{
typeof(SingleField).GetReferencedTypes()
.Should().Have.SameValuesAs(
common.Union(new[]
{
typeof(DateTime)
})
);
}
[Test]
public void GetReferencedTypes_SingleProperty()
{
typeof(SingleProperty).GetReferencedTypes()
.Should().Have.SameValuesAs(
common.Union(new[]
{
typeof(DateTime),
typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute)
})
);
}
[Test]
public void GetReferencedTypes_SingleNonAutoProperty()
{
typeof(SingleNonAutoProperty).GetReferencedTypes()
.Should().Have.SameValuesAs(
common.Union(new[]
{
typeof(DateTime)
})
);
}
[Test]
public void GetReferencedTypes_FeeMethod()
{
typeof(FeeMethod).GetReferencedTypes()
.Should().Have.SameValuesAs(
common.Union(new[]
{
typeof(Fee)
})
);
}
[Test]
public void GetReferencedTypes_SingleArgVoidMethod()
{
typeof(SingleArgVoidMethod).GetReferencedTypes()
.Should().Have.SameValuesAs(
common.Union(new[]
{
typeof(Fee)
})
);
}
[Test]
public void GetReferencedTypes_SingleArgCtor()
{
typeof(SingleArgCtor).GetReferencedTypes()
.Should().Have.SameValuesAs(
common.Union(new[]
{
typeof(Fee)
})
);
}
[Test]
public void GetReferencedTypes_ExceptionRaiser()
{
typeof(ExceptionRaiser).GetReferencedTypes()
.Should().Have.SameValuesAs(
common.Union(new[]
{
typeof(Exception)
})
);
}
[Test]
public void GetReferencedTypes_Attributes()
{
typeof(Attributes).GetReferencedTypes()
.Should().Have.SameValuesAs(
common.Union(new []
{
typeof(SerializableAttribute), // Class Attribute
typeof(NonSerializedAttribute), // Field Attribute
typeof(FooAttribute), // MethodAttribute
typeof(FooAttribute2) // Parameter Attribute
})
);
}
}
// ReSharper restore InconsistentNaming
}
Essas foram as principais alterações:
Adicionei algumas consultas adicionais para a coleta do indicador Ce. Veja:
using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
namespace FluentCodeMetrics.Core
{
public static class CeExtensions
{
public static int ComputeCe(this Type that)
{
return that
.GetReferencedTypes()
.Count();
}
public static IEnumerable<Type>
GetReferencedTypes(this Type that)
{
var flags = BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public
;
var typeMetaAttributeTypes =
from attribute in that.GetCustomAttributes(true)
select attribute.GetType();
var fieldMetaAttributeTypes =
from field in that.GetFields(flags)
from attribute in field.GetCustomAttributes(true)
select attribute.GetType();
var methodMetaAttributeTypes =
from method in that.GetMethods(flags)
from attribute in method.GetCustomAttributes(true)
select attribute.GetType();
var methodParameterMetaAttributeTypes =
from method in that.GetMethods(flags)
from parameter in method.GetParameters()
from attribute in parameter.GetCustomAttributes(true)
select attribute.GetType();
var fieldTypes =
from field in that.GetFields(flags)
select field.FieldType;
var propertyTypes =
from property in that.GetProperties(flags)
select property.PropertyType;
var methodReturnTypes =
from method in that.GetMethods(flags)
where method.ReturnType != typeof(void)
select method.ReturnType;
var methodParameterTypes =
from method in that.GetMethods(flags)
from parameter in method.GetParameters()
select parameter.ParameterType;
var ctorParameterTypes =
from ctor in that.GetConstructors(flags)
from parameter in ctor.GetParameters()
select parameter.ParameterType;
return new[] { that.BaseType }
.Union(typeMetaAttributeTypes)
.Union(fieldMetaAttributeTypes)
.Union(methodMetaAttributeTypes)
.Union(methodParameterMetaAttributeTypes)
.Union(ctorParameterTypes)
.Union(methodParameterTypes)
.Union(fieldTypes)
.Union(propertyTypes)
.Union(methodReturnTypes)
.Distinct();
}
}
}
O que você acha desse código?! Alguma sugestão de melhoria?!
Há quem defenda que Ce deveria ser medido para um pacote (namespace). Também há quem defenda que seja medido para Assemblies. Farei isso, mas em outras implementações.
Por agora, entendo que ainda é necessário suportar Exceptions. Veja:
Enfim, a coisa tá ficando legal.
BTW, o Juan Lopes melhorou ainda mais o processo de build.
Era isso.