Elemar DEV

Tecnologia e desenvolvimento

FluentCodeMetrics – Parte 6 – TypeSets

Olá pessoal. Tudo certo?!

Até aqui, calculamos Ce para tipos individualmente. Mas, como proceder para calcular Ce para outras “unidades de análise”. Por exemplo, para um assembly ou namespace.

Nesse post, apresento como implementei esse importante avanço no FluentCodeMetrics (veja outros posts relacionados).

Lembre-se: todo código-fonte para FluentCodeMetrics está no Github.

DISCLAIMER: Adoraria sugestões de design. Veja o repositório para entender o projeto e, se possível, me ajude.

Cenários que desejamos implementar

Basicamente, pretendo implementar dois cenários novos:

1. Desejo poder restringir o cálculo do Ce para referências com tipos de determinados assemblies.

 

image

2. Desejo calcular Ce para um conjunto de tipos, por exemplo, para um assembly/namespace.

image

Pegou a idéia?!

Filtro de Assembly

Vamos começar atendendo o primeiro cenário. Começamos com um novo TypeFilter. Veja:

using System;
using System.Reflection;

namespace FluentCodeMetrics.Core.TypeFilters
{
    public class FromAssemblyTypeFilter : TypeFilter
    {
        private readonly Assembly assemblyField;

        internal FromAssemblyTypeFilter(Assembly assembly)
        {
            assemblyField = assembly;
        }

        public override bool Check(Type type)
        {
            return type.Assembly == assemblyField;
        }
    }
}

Além disso, mudei o TypeFilter (veja o código completo no github) para criar um “method helper”. Veja:

public static implicit operator TypeFilter(Assembly assembly)
{
    return FromAssembly(assembly);
}

public static TypeFilter FromAssembly(Assembly assembly)
{
    return new FromAssemblyTypeFilter(assembly);
}

Agora, para inferir as Ce para uma classe, apenas considerando referências para tipos do mesmo assembly, basta escrever algo assim:

Ce.For<Foo>()
    .Ignoring(typeof(Foo).GetType().Assembly.Not());

TypeSet

Já conseguimos verificar Ce para tipos individuais. Entretanto, muitas vezes, desejaremos trabalhar com um conjunto de tipos (um assembly inteiro, ou um namespace).

Para isso, criei o conceito de TypeSet. Veja:

using System;
using System.Reflection;
using System.Collections.Generic;

namespace FluentCodeMetrics.Core.TypeSets
{
    public abstract class TypeSet : IEnumerable<Type>
    {
        public abstract IEnumerable<Type> GetAllTypes();

        public IEnumerator<Type> GetEnumerator()
        {
            return GetAllTypes().GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetAllTypes().GetEnumerator();
        }

        public static TypeSet FromAssembly(Assembly assembly)
        {
            return new CollectionTypeSet(assembly.GetTypes());
                // AssemblyTypeSet(assembly);
        }

        public static TypeSet With(params Type[] types)
        {
            return new CollectionTypeSet(types);
        }

        public static implicit operator TypeSet(Assembly source)
        {
            return FromAssembly(source);
        }
    }
}

Essa classe base tem uma especialização (por agora). Veja:

using System;
using System.Collections.Generic;

namespace FluentCodeMetrics.Core.TypeSets
{
    public class CollectionTypeSet : TypeSet
    {
        private IEnumerable<Type> source; 
        public CollectionTypeSet(IEnumerable<Type> source)
        {
            this.source = source;
        }

        public override IEnumerable<Type> GetAllTypes()
        {
            return source;
        }
    }
}

Aproveitando, também criei um TypeFilter para esse TypeSet. Veja:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FluentCodeMetrics.Core.TypeFilters
{
    public class CollectionTypeFilter : TypeFilter
    {
        private readonly IEnumerable<Type> typesField;

        internal CollectionTypeFilter(IEnumerable<Type> types)
        {
            typesField = types;
        }

        public override bool Check(Type type)
        {
            return typesField.Contains(type);
        }
    }
}

Com mais alguns métodos auxiliares, posso escrever um filtro para diversos tipos dessas duas formas:

using System;

using NUnit.Framework;
using SharpTestsEx;
using FluentCodeMetrics.Core;
using FluentCodeMetrics.Core.TypeSets;

namespace FluentCodeMetrics.Tests
{
    [TestFixture]
    public class CeTests
    {
        [Test]
        public void Ce_ForSingleArgCtorIgnoringCommonTypes()
        {
            var ce = Ce.For<Samples.SingleArgCtor>()
                .Ignoring<System.Runtime.TargetedPatchingOptOutAttribute>()
                .Ignoring<System.Security.SecuritySafeCriticalAttribute>()
                .Ignoring<System.Runtime.ConstrainedExecution.ReliabilityContractAttribute>()
                .Ignoring<System.Runtime.CompilerServices.CompilerGeneratedAttribute>()
                .Ignoring<System.Object>()
                .Ignoring<System.Int32>()
                .Ignoring<System.String>()
                .Ignoring<System.Boolean>()
                .Ignoring<System.Type>();

            ce.Value.Should().Be(1);
            ce.References.Should().Have.SameValuesAs(
                new[] {typeof (Samples.Fee)}
                );
        }

        [Test]
        public void Ce_ForSingleArgCtorIgnoringTypeSet()
        {
            var ce = Ce.For<Samples.SingleArgCtor>()
                .Ignoring(TypeSet.With(
                    typeof(System.Runtime.TargetedPatchingOptOutAttribute),
                    typeof(System.Security.SecuritySafeCriticalAttribute),
                    typeof(System.Runtime.ConstrainedExecution.ReliabilityContractAttribute),
                    typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute),
                    typeof(object),
                    typeof(int),
                    typeof(string),
                    typeof(bool),
                    typeof(Type)
                ));

            ce.Value.Should().Be(1);
            ce.References.Should().Have.SameValuesAs(
                new[] { typeof(Samples.Fee) }
                );
        }

        [Test]
        public void Ce_ForSingleArgCtorIgnoringOtherAssembliesTypes()
        {
            var ce = Ce.For<Samples.SingleArgCtor>()
                .Ignoring(typeof (Samples.SingleArgCtor).Assembly.Not());
                
            ce.Value.Should().Be(1);
            ce.References.Should().Have.SameValuesAs(
                new[] { typeof(Samples.Fee) }
                );
        }
    }
}

Ce suportando tipos e/ou TypeSets

Agora temos condições de rever o design do Ce para que este suporte tipos individuais ou conjuntos. Veja:

using System;
using System.Linq;

using FluentCodeMetrics.Core.TypeFilters;
using FluentCodeMetrics.Core.TypeSets;
using System.Collections.Generic;

namespace FluentCodeMetrics.Core
{
    public abstract class Ce
    {
        public int Value
        {
            get { return References.Count(); }
        }

        public abstract ReferencedTypesTypeSet References { get; }

        public static implicit operator int(Ce source)
        {
            return source.Value;
        }

        public abstract Ce FilterBy(TypeFilter filter);

        public Ce Ignoring(TypeFilter toIgnore)
        {
            return FilterBy(toIgnore.Not());
        }

        public Ce Ignoring<T>()
        {
            return Ignoring(typeof(T));
        }

        public static TypeSetCe For(TypeSet typeSet)
        {
            var source = from type in typeSet
                         select For(type);

            return new TypeSetCe(source);
        }

        public static TypeCe For(Type type)
        {
            var references = ReferencesInspector.For(type)
                .Where(type.NestedTypes().Not());
            return new TypeCe(references, type);
        }

        public static TypeCe For<T>()
        {
            return For(typeof (T));
        }
    }

   
    public class TypeCe : Ce
    {
        private readonly ReferencedTypesTypeSet references;
        private readonly Type type;
        public Type Type
        {
            get { return type;  }
        }

        internal TypeCe(ReferencedTypesTypeSet refs, Type type)
        {   
            references = refs;
            this.type = type;
        }

        public override ReferencedTypesTypeSet References
        {
            get { return references; }
        }

        public override Ce FilterBy(TypeFilter filter)
        {
            return filter == null ?
                this :
                new TypeCe(references.FilterBy(filter), Type);
        }
    }

    public class TypeSetCe : Ce
    {
        private IEnumerable<Ce> source;

        public IEnumerable<Ce> Source
        {
            get { return source; }
        }

        internal TypeSetCe(IEnumerable<Ce> source)
        {
            this.source = source;
        }
        
        public override ReferencedTypesTypeSet References
        {
            get
            {
                var allReferences = from member in source
                                    from reference in member.References
                                    select reference;
                return new ReferencedTypesTypeSet(allReferences.Distinct(), null);
            }
        }

        public override Ce FilterBy(TypeFilter filter)
        {
            var newSource = source.Select(ceResult => ceResult.FilterBy(filter)).ToList();
            return new TypeSetCe(newSource);
        }
    }
}

Pegou a idéia?!

Era isso.

2 comentários em “FluentCodeMetrics – Parte 6 – TypeSets

  1. oenning
    21/05/2012

    Elemar, quero colaborar com duas alterações.

    A primeira é a adição de um novo exemplo que falha (já fiz a correção também).
    A segunda é uma sugestão de refatoração bem simples.

    Fico em dúvida quanto ao segundo item. Talvez você pode não goste da refatoração. Como devo proceder? Posso mandar um Pull Request e você analisa se é válido ou não? Devo enviar dois Pulls separados? Abro uma Issue para discussão?

    Sou novo no Git, principalmente quando o assunto é colaboração, por isso ainda estou perdido, qualquer dica é bem vinda.

    Abraço.

    • elemarjr
      21/05/2012

      Olá Guilherme,

      Antes de qualquer coisa, obrigado pelo interesse em colaborar.

      Se não for muito trabalho, mande pull requests separados.

      Refactoring é sempre bom :D

Deixe uma resposta

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

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

Informação

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

Estatísticas

  • 585,408 hits
%d blogueiros gostam disto: