CodeCracker – Minha primeira colaboração.

Olá pessoal tudo bem ?

Estou participando de um projeto open source tupiniquim, o projeto que estou participando é o CodeCraker, se você ainda não conhece, segue o link do post do Elemar falando um pouco sobre o projeto http://elemarjr.net/2014/11/15/codecracker-parte-1-o-que-e-como-colaborar/.

A minha primeira missão foi trabalhar em uma tarefa de refatoração, quando se trata de designer de código fico realmente muito empolgado em trabalhar em tarefas desse tipo, abaixo o motivo da missão e a descrição da tarefa.

Motivo:

E muito comum em um desenvolvimento de software  aparecerem métodos que possui uma quantidade exagerada de parâmetros.

um método com essa quantidade exagerada de parâmetros não aumenta apenas o tamanho do método, o pior problema e que aumenta a complexidade de leitura e o entendimento do método.

Exemplo de um método que deveria ser refatorado;

public void MyMethod(string a,string b,string c,string d,string e,string f)
{

}

photo_martin_r_3 Uncle Bob nos alerta muito bem sobre esse risco em um dos seus melhores livros na minha opinião o “Clean Code”.

O tio Bob diz que “Quando um método possui mais de 3 parâmetros, ele começa a ficar confuso e também com isso aumenta a complexidade de leitura do  código”.

Então ouvindo os sábios conselhos do Tio Bob, resolvi enviar uma sugestão para o pessoal do CodeCracker;

Link da Sugestão: https://github.com/code-cracker/code-cracker/issues/44

sugestao

Desenvolvendo o Analyzing:
 [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class ParameterRefactoryAnalyzer : DiagnosticAnalyzer
    {
        public const string DiagnosticId = "CC0020";
        internal const string Title = "You should using 'new class'";
        internal const string MessageFormat = "When the method has more than three parameters, use new class.";
        internal const string Category = "Syntax";

        internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true);

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }

        public override void Initialize(AnalysisContext context)
        {
            context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.MethodDeclaration);
        }

        private void AnalyzeNode(SyntaxNodeAnalysisContext context)
        {
            var method = (MethodDeclarationSyntax)context.Node;

            var contentParameter = method.ParameterList;

            if (!contentParameter.Parameters.Any() || contentParameter.Parameters.Count <= 3) return;

            if (method.Body?.ChildNodes().Count() > 0) return;


            var diagnostic = Diagnostic.Create(Rule, contentParameter.GetLocation());

            context.ReportDiagnostic(diagnostic);
        }
    }
Desenvolvendo o Fixing:
 [ExportCodeFixProvider("ParameterRefactoryCodeFixProvider", LanguageNames.CSharp), Shared]
    public class ParameterRefactoryCodeFixProvider : CodeFixProvider
    {
 
        public sealed override ImmutableArray<string> GetFixableDiagnosticIds()
        {
            return ImmutableArray.Create(ParameterRefactoryAnalyzer.DiagnosticId);
        }

        public sealed override FixAllProvider GetFixAllProvider()
        {
            return WellKnownFixAllProviders.BatchFixer;
        }

        public sealed override async Task ComputeFixesAsync(CodeFixContext context)
        {
            var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

            var diagnosticClass = context.Diagnostics.First();
            var diagnosticSpanClass = diagnosticClass.Location.SourceSpan;
            var declarationClass = root.FindToken(diagnosticSpanClass.Start).Parent.AncestorsAndSelf().OfType<ClassDeclarationSyntax>().First();

            var diagnosticNameSpace = context.Diagnostics.First();
            var diagnosticSpanNameSpace = diagnosticNameSpace.Location.SourceSpan;
            var declarationNameSpace = root.FindToken(diagnosticSpanNameSpace.Start).Parent.AncestorsAndSelf().OfType<NamespaceDeclarationSyntax>().FirstOrDefault();


            var diagnosticMethod = context.Diagnostics.First();
            var diagnosticSpanMethod = diagnosticMethod.Location.SourceSpan;
            var declarationMethod = root.FindToken(diagnosticSpanClass.Start).Parent.AncestorsAndSelf().OfType<MethodDeclarationSyntax>().First();

            context.RegisterFix(CodeAction.Create("Change to new CLass", c => NewClassAsync(context.Document, declarationNameSpace, declarationClass, declarationMethod, c)), diagnosticClass);

        }

        private async Task<Document> NewClassAsync(Document document, NamespaceDeclarationSyntax OldNameSpace, ClassDeclarationSyntax oldClass, MethodDeclarationSyntax oldMethod, CancellationToken cancellationToken)
        {
            var root = await document.GetSyntaxRootAsync();
            SyntaxNode newRootParameter = null;


            if (OldNameSpace == null)
            {
                var newCompilation = NewCompilationFactory((CompilationUnitSyntax)oldClass.Parent, oldClass, oldMethod);

                newRootParameter = root.ReplaceNode((CompilationUnitSyntax)oldClass.Parent, newCompilation);

                return document.WithSyntaxRoot(newRootParameter);

            }

            var newNameSpace = NewNameSpaceFactory(OldNameSpace, oldClass, oldMethod);

            newRootParameter = root.ReplaceNode(OldNameSpace, newNameSpace);

            return document.WithSyntaxRoot(newRootParameter);

        }

        private static List<PropertyDeclarationSyntax> NewPropertyClassFactory(MethodDeclarationSyntax methodOld)
        {
            var newGetSyntax = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
                                           .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));


            var newSetSyntax = SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
                                            .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));

            var acessorSyntax = SyntaxFactory.AccessorList(
                                SyntaxFactory.Token(SyntaxKind.OpenBraceToken),
                                SyntaxFactory.List(new[] { newGetSyntax, newSetSyntax }),
                                SyntaxFactory.Token(SyntaxKind.CloseBraceToken));


            var propertys = new List<PropertyDeclarationSyntax>();

            foreach (ParameterSyntax param in methodOld.ParameterList.Parameters)
            {
                var property = SyntaxFactory.PropertyDeclaration(
                                default(SyntaxList<AttributeListSyntax>),
                                SyntaxFactory.TokenList(new[] { SyntaxFactory.Token(SyntaxKind.PublicKeyword) }),
                                param.Type,
                                default(ExplicitInterfaceSpecifierSyntax),
                                SyntaxFactory.Identifier(FirstLetteToUpper(param.Identifier.Text)),
                                acessorSyntax);

                propertys.Add(property);

            }

            return propertys;
        }

        private static ClassDeclarationSyntax NewClassParameterFactory(string newNameClass, List<PropertyDeclarationSyntax> Property)
        {
            return SyntaxFactory.ClassDeclaration(newNameClass)
                                                  .WithMembers(SyntaxFactory.List<MemberDeclarationSyntax>(Property))
                                                  .WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword)))
                                                  .WithAdditionalAnnotations(Formatter.Annotation);

        }

        private static NamespaceDeclarationSyntax NewNameSpaceFactory(NamespaceDeclarationSyntax OldNameSpace, ClassDeclarationSyntax OldClass, MethodDeclarationSyntax OldMethod)
        {


            var newNameSpace = OldNameSpace;

            var className = "NewClass\{OldMethod.Identifier.Text}";

            var memberNameSpaceOld = (from member in OldNameSpace.Members
                                      where member == OldClass
                                      select member).FirstOrDefault();

            newNameSpace = OldNameSpace.ReplaceNode(memberNameSpaceOld, NewClassFactory(className, OldClass, OldMethod));

            var newParameterClass = NewClassParameterFactory(className, NewPropertyClassFactory(OldMethod));

            newNameSpace = newNameSpace
                            .WithMembers(newNameSpace.Members.Add(newParameterClass))
                            .WithAdditionalAnnotations(Formatter.Annotation);

            return newNameSpace;

        }

        private static ClassDeclarationSyntax NewClassFactory(string className, ClassDeclarationSyntax classOld, MethodDeclarationSyntax methodOld)
        {

            var newParameter = SyntaxFactory.Parameter(SyntaxFactory.Identifier("\{className} \{FirstLetteToLower(className)}"));

            var paremeters = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList<ParameterSyntax>().Add(newParameter))
                .WithAdditionalAnnotations(Formatter.Annotation);


            var newMethod = SyntaxFactory.MethodDeclaration(methodOld.ReturnType, methodOld.Identifier.Text)
                        .WithModifiers(methodOld.Modifiers)
                        .WithParameterList(paremeters)
                        .WithBody(methodOld.Body)
                        .WithAdditionalAnnotations(Formatter.Annotation);

            var newClass = classOld.ReplaceNode(methodOld, newMethod);

            return newClass;
        }

        private static CompilationUnitSyntax NewCompilationFactory(CompilationUnitSyntax OldCompilation, ClassDeclarationSyntax OldClass, MethodDeclarationSyntax OldMethod)
        {
            var newNameSpace = OldCompilation;


            var className = "NewClass\{OldMethod.Identifier.Text}";

            var OldMemberNameSpace = (from member in OldCompilation.Members
                                      where member == OldClass
                                      select member).FirstOrDefault();

            newNameSpace = OldCompilation.ReplaceNode(OldMemberNameSpace, NewClassFactory(className, OldClass, OldMethod));

            var newParameterClass = NewClassParameterFactory(className, NewPropertyClassFactory(OldMethod));

            return newNameSpace.WithMembers(newNameSpace.Members.Add(newParameterClass))
                                .WithAdditionalAnnotations(Formatter.Annotation);

        }

        private static string FirstLetteToUpper(string text)
        {
            return string.Concat(text.Replace(text[0].ToString(), text[0].ToString().ToUpper()));
        }
        private static string FirstLetteToLower(string text)
        {
            return string.Concat(text.Replace(text[0].ToString(), text[0].ToString().ToLower()));
        }

    }

Analyzing: Durante o desenvolvimento do analyzing não encontrei dificuldade, como podemos ver no código e bem tranquilo.

Fixing: Durante o desenvolvimento, realmente foi um desafio, precisei criar uma nova class com os parâmetros do método, precisei alterar o método atual trocando os parâmetros antigos pelo novo parâmetro.

Todo código é culpado até que se prove o contrário.

    public class ParameterRefectoryTest : CodeFixTest<ParameterRefactoryAnalyzer, ParameterRefactoryCodeFixProvider>
    {

        [Fact]
        public async Task WhenMethodDoesNotThreeParametersNotSuggestionANewClass()
        {
            const string test = @"
    using System;

    namespace ConsoleApplication1
    {
        class TypeName
        {
            public void Foo(string name,string age,string day)
            {
               
            }
        }
    }";
            await VerifyCSharpHasNoDiagnosticsAsync(test);

        }

        [Fact]
        public async Task WhenMethodHasElementBodyAndHasMoreThreeParametersShouldNotSuggestionANewClass()
        {
            const string test = @"
    using System;

    namespace ConsoleApplication1
    {
        class TypeName
        {
            public void Foo(string name,string age,int day,int year)
            {
                if(true)
                {
                   day = 10;
                }
            }
        }
    }";
            await VerifyCSharpHasNoDiagnosticsAsync(test);

        }


        [Fact]
        public async Task ShouldUpdateParameterToClass()
        {
            const string oldTest = @"
using System;

namespace ConsoleApplication1
{
    class TypeName
    {
        public void Foo(string a, string b, int year, string d)
        {

        }
    }

}";
            const string newTest = @"
using System;

namespace ConsoleApplication1
{
    class TypeName
    {
        public void Foo(NewClassFoo newClassFoo)
        {

        }
    }

    public class NewClassFoo
    {
        public string A { get; set; }
        public string B { get; set; }
        public int Year { get; set; }
        public string D { get; set; }
    }
}";
            await VerifyCSharpFixAsync(oldTest, newTest, 0);



        }

        [Fact]
        public async Task WhenHasNotNameSpaceShouldGenerateClassParameter()
        {
            const string oldTest = @"
using System;

class TypeName
{
    public void Foo(string a, string b, int year, string d)
    {

    }
}
";
            const string newTest = @"
using System;

class TypeName
{
    public void Foo(NewClassFoo newClassFoo)
    {

    }
}

public class NewClassFoo
{
    public string A { get; set; }
    public string B { get; set; }
    public int Year { get; set; }
    public string D { get; set; }
}";

            await VerifyCSharpFixAsync(oldTest, newTest, 0);



        }

        [Fact]
        public async void ShouldGenerateNewClassFoo2()
        {
            const string oldTest = @"
using System;

namespace ConsoleApplication1
{
    class TypeName
    {
        public void Foo(NewClassFoo newClassFoo)
        {

        }

        public void Foo2(string a, string b, int year, string d)
        {

        }
    }

    public class NewClassFoo
    {
        public string A { get; set; }
        public string B { get; set; }
        public int Year { get; set; }
        public string D { get; set; }
    }

}";
            const string newTest = @"
using System;

namespace ConsoleApplication1
{
    class TypeName
    {
        public void Foo(NewClassFoo newClassFoo)
        {

        }

        public void Foo2(NewClassFoo2 newClassFoo2)
        {

        }
    }

    public class NewClassFoo
    {
        public string A { get; set; }
        public string B { get; set; }
        public int Year { get; set; }
        public string D { get; set; }
    }

    public class NewClassFoo2
    {
        public string A { get; set; }
        public string B { get; set; }
        public int Year { get; set; }
        public string D { get; set; }
    }
}";
            await VerifyCSharpFixAsync(oldTest, newTest, 0);


        }

    }

 

Resultado da brincadeira:

resultado

Conclusão: Bom pessoal participar de um projeto desse porte  open source e com pessoas de alto nível e realmente enriquecedor, e o melhor ainda e um projeto tupiniquim :)

LEAVE A COMMENT