ILMerge

Ontem durante o Ask The Experts no TechEd Brasil 2009, alguém perguntou se era possível combinar diversos assemblies em um só para não ter que distribuí-los separadamente.

Um exemplo disto seria pegar um executável como MeuExecutavel.exe e alguns assemblies dependentes como MinhaDllA.dll e MinhaDllB.dll  e juntá-os todos em um único assembly como MeuAssemblao.exe.

A resposta é: Sim é possível e para tanto você pode usar o ILMerge

Occam’s razor

Eu já vinha escutando o podcast do pessoal do StackOverflow há alguns meses e por conta disto já tinha ouvido falar no site.

Cheguei a visitar o site algumas vezes, mas nunca gastei muito tempo por lá. Hoje enfim decidi me registrar.

O login via Open ID é bem bacana – integra com sua conta no GMail e alguns outros. Pena que não com o Windows Live ID.

Mas chega de enrolação e vamos para o assunto de hoje:

Dando uma olhada nos posts de C#, eu acabei cruzando o tópico “How could I refactor this factory-type method and database call to be testable?

Todas as sugestões dadas envolvem uma reestruturação radical do código. O pobre coitado deve ter ficado assustado. Eu estou!

Por coincidência, eu venho trabalhando bastante com testes unitários ultimamente e uma das necessidades que tenho é justamente a de isolar os testes do banco de dados.

O problema é que eu ainda não estou convencido de que se deva sair alterando todo o sistema só para substituir o banco de dados por um dublê, mock ou o que quer que você chame.

Deve ter um jeito mais fácil de fazer isto.

Pau que nasce torto não cresce direito

 

Uma prática bastante comum no desenvolvimento de sistemas é o que eu chamo de CPOP [1]– “Copy & Paste Oriented Programming”.

A prática é bastante utilizada, pois fornece um template inicial a partir da qual o módulo sendo trabalhado é rapidamente reproduzido e adaptado. Com isto economiza-se tempo no desenvolvimento.

Por conta CPOP, é de suma importância que o modelo original tenha a melhor qualidade possível.

Vamos pegar um exemplo: O “Guia do Desenvolvimento” de um determinado projeto em que trabalhei que dava o seguinte modelo para a implementação das classes na camada de acesso a dados.

Friend Class AD_GrupoSerie

 

    Private Sub New()

    End Sub

 

    Public Shared Function Construtor(ByVal reader As IDataRecord) As EN_GrupoSerie

 

        If reader Is Nothing Then

            Throw New ArgumentNullException("reader")

        End If

 

        ‘ Obtém posição dos campos no data reader

        Dim iGrupoSerieIdx As Integer = reader.GetOrdinal("COD_GRUPO_SERIE")

        Dim iNomGrupoSerieIdx As Integer = reader.GetOrdinal("NOM_GRUPO_SERIE")

 

        ‘ Cria objeto da entidade de negócio

        Dim objGrupoSerie As EN_GrupoSerie = New EN_GrupoSerie()

 

        ‘ Alimenta propriedades da entidade de negócio

        objGrupoSerie.Codigo = reader.GetInt32(iGrupoSerieIdx)

 

        ‘ Para campo que pode ser nulo, é necessário atribuir valor default.

        If Not reader.IsDBNull(iNomGrupoSerieIdx) Then

            objGrupoSerie.Nome = reader.GetString(iNomGrupoSerieIdx)

        Else

            objGrupoSerie.Nome = String.Empty

        End If

 

        ‘ Retorna objeto que representa a entidade de negócio

        Return objGrupoSerie

 

    End Function

 

End Class

 

O código tem uma coisa legal que é a validação do parâmetro de entrada “reader” (isto é menos comum no código deste projeto do que deveria ser), mas o código tem oportunidades de melhoria tanto quando olhado isoladamente quando olhada no contexto em que o método é usado.

DMTCRI[2]

Primeiro vamos ao problema que considero mais relevante que é o contexto onde o método é usado: Este método é chamado pelo método AD_GrupoSerie_Acao.Selecionar dentro de um loop – uma vez para cada registro retornado do banco de dados.

Public Class AD_GrupoSerie_Acao

    Public Function Selecionar(ByVal strNome As String) As List(Of EN_GrupoSerie)

 

        Dim resultado As List(Of EN_GrupoSerie) = New List(Of EN_GrupoSerie)()

 

        Dim db As Database = DatabaseFactory.CreateDatabase(strDBNome)

 

        If db Is Nothing Then

            Throw New ArgumentNullException("db")

        End If

 

        Dim command As DbCommand = db.GetStoredProcCommand("dbo.SPPJ_SELECIONAR_GRUPOSERIE")

 

        ‘ Alimenta parâmetros da pesquisa

        db.AddInParameter(command, "P_NOM_GRUPOSERIE", DbType.String, strNome)

 

        Dim rdr As IDataReader = db.ExecuteReader(command)

 

        While rdr.Read()

            resultado.Add(AD_GrupoSerie.Construtor(rdr))

        End While

 

        Return resultado

 

    End Function

 

End Class

 

Acontece que parte das operações realizadas dentro do método Construtor avaliam coisas que são imutáveis durante o tempo de vida do loop.

Uma vez que o código entre no loop, reader nunca será Nothing e a posição das colunas nunca vai mudar. A gente está simplesmente fazendo o código repetir a se mesmo. Podemos evitar isto movendo o código que trata destas duas coisas para fora do loop.

Friend Class AD_GrupoSerie

    Private reader As IDataRecord

    Private iGrupoSerieIdx As Integer

    Private iNomGrupoSerieIdx As Integer

    Private iIndAtivoIdx As Integer

 

    Public Sub New(ByVal reader As IDataRecord)

        If reader Is Nothing Then

            Throw New ArgumentNullException("reader")

        End If

        Me.reader = reader

 

        ‘ Obtém posição dos campos no data reader

        iGrupoSerieIdx = reader.GetOrdinal("COD_GRUPO_SERIE")

        iNomGrupoSerieIdx = reader.GetOrdinal("NOM_GRUPO_SERIE")

        iIndAtivoIdx = reader.GetOrdinal("IND_ATIVO")

    End Sub

 

    Public Function Construir() As EN_GrupoSerie

        ‘ Cria objeto da entidade de negócio

        Dim objGrupoSerie As EN_GrupoSerie = New EN_GrupoSerie()

 

        ‘ Alimenta propriedades da entidade de negócio

        objGrupoSerie.Codigo = reader.GetInt32(iGrupoSerieIdx)

 

        ‘ Para campo que pode ser nulo, é necessário atribuir valor

        If Not reader.IsDBNull(iNomGrupoSerieIdx) Then

            objGrupoSerie.Nome = reader.GetString(iNomGrupoSerieIdx)

            objGrupoSerie.IndAtivo = reader.GetBoolean(iIndAtivoIdx)

        Else

            objGrupoSerie.Nome = String.Empty

        End If

 

        ‘ Retorna objeto que representa a entidade de negócio

        Return objGrupoSerie

    End Function

End Class

 

    Public Function Selecionar(ByVal strNome As String, ByVal iAtivo As Integer) As List(Of EN_GrupoSerie)

        Dim db As Database = DatabaseFactory.CreateDatabase(strDBNome)

 

        Using command As DbCommand = db.GetStoredProcCommand("dbo.SPPJ_SEL_GRUPO_SERIE")

            db.AddInParameter(command, "P_NOM_GRUPOSERIE", DbType.String, strNome)

            db.AddInParameter(command, "P_IND_ATIVO", DbType.Int32, iAtivo)

 

            Dim resultado As List(Of EN_GrupoSerie) = New List(Of EN_GrupoSerie)()

            Using reader As IDataReader = db.ExecuteReader(command)

                Dim factory As New AD_GrupoSerie(reader)

                While reader.Read()

                    resultado.Add(factory.Construir())

                End While

            End Using

            Return resultado

        End Using

    End Function

As operações foram movidas para o construtor de AD_GrupoSerie que está fora do loop em AD_GrupoSerie_Acao.Selecionar e com isto não se tem o custo de verificação do parâmetro por nulo e a localização da posição das colunas para cada linha do resultset.

Dado ao fato do pessoal do projeto usar CPOP a torto e a direito, o mesmo erro foi replicado em dezenas de locais diferentes. Sair consertando tudo é inviável financeiramente, então é importante revisar no detalhe qualquer código que possa vir a ser utilizado como original (template) para o CPOP.


[1] Ao procurar o verbete para DRY no Wikipedia, eu acabei encontrando uma entrada para “Copy and paste programing” que trata justamente do que eu vinha chamando de CPOP – Copy & Paste Oriented Programming. Eu ainda acho CPOP mais legal por conta da alusão a OOP.

Por falar em OOP, a idéia não é fazer uma apologia ao CPOP. Se o cenário permitir, DRY nele!

[2] DMTCRI – Don’t Make The Code Repeat Itself é uma alusão a DRY – Don’t Repeat Yourself, uma filosofia de programação que prega a redução da duplicação de código. Aqui a idéia que quero passar é a de se evitar fazer com que o programa execute repetidas vezes operações que sempre trarão os mesmos resultados.

We need a tool that combines functionality of xsd.exe and sgen.exe

I added a suggestion on Microsoft Connect asking for something with the combined functionality of xsd.exe and sgen.exe.

Given an xml schema file (xsd), you could use xsd.exe to generate a set of classes that could be serialized in conformance with that schema.

Then to perf things up a little bit, you could use sgen.exe to create an assembly containing specialized serializers for the classes generated previously.

You would end up with two assemblies for something that, in my opinion, would be better handled with one. For instance, with only one assembly there would be no risks of mismatching versions of the Serializable and Serializer assemblies.

You can work around having an extra assembly using sgen’s “/k” option which keeps on your project’s folder the temporary files used to create the serializer assembly. Among those files, there’s one with the source code.

There are some caveats though:

First, sgen only generates code in C#. If your project is in Visual Basic, then you have to take some extra steps to integrate the code into your code base.

Second, each time you run sgen, it creates a new file with a random name. You’ll have to rename it to something that’s stable enough to be used in your project. This can be done with some pre or post build events, but things can eventually get wrong.

 

If you feel my pain, please vote on:

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=416020

TF26212: Team Foundation Server could not save your changes.

I created a new WIT (work item type) to track progress of a set of activities we have here. Let’s call it “Setup”.

I wanted to restrict the users that create a new instance of the work item. The way to do this in TFS is to put a restriction on the state transition from “” to the first state of your workflow: “FirstStep” in our case.

To make things easier, I gave the group the same name of the WIT: “Setup”.

So I ended with something along the following lines:

<?xml version="1.0" encoding="utf-8"?>

<witd:WITD application="Work item type editor" version="1.0" xmlns:witd="http://schemas.microsoft.com/VisualStudio/2005/workitemtracking/typedef">

  <WORKITEMTYPE name="Setup">

    <WORKFLOW>

      <STATES />

      <TRANSITIONS>

        <TRANSITION from="" to="FirstStep" for="[project]Setup" />

      </TRANSITIONS>

    </WORKFLOW>

  </WORKITEMTYPE>

</witd:WITD>

 

The problem is that when I tried to save a new instance of the work item, I received the following message from TFS:

TF26212: Team Foundation Server could not save your changes. There may be problems with the work item type definition. Try again or contact your Team Foundation Server administrator.

That really doesn’t help…

After some research I found a blog post with a solution to the problem.

The problem is you should not have a WIT with the same name of a TFS group in whatever Team Projects you have.

You probably shouldn’t be using ReDim Preserve inside a loop

These days I was doing a code review when I went across a method which contained a ReDim Preserve inside a loop.

I can’t show the real code because it is from a customer’s code base, but here’s a simplification of what I found:

    Sub CopyAndPasteOrientedProgramming()

        Dim c As New Customer()

        For i As Integer = 0 To 250000

            ReDim Preserve c.Orders(c.Orders.Length + 1)

            c.Orders(c.Orders.Length – 1) = New Order()

        Next

        Console.WriteLine(c.Orders.Length)

    End Sub

 

I found the developer who checked in the code and went on to tell him to use a List(Of Order) and he replied “Oh, don’t bother with that. It’s just a piece of code that I copied from a similar class and since it doesn’t have a business rule for adding the elements, I took off the If statement.

Wait a minute, I said. You’re telling me there’s more code like this scattered throughout the code base?

Yup, he said.

So to simplify, the business rule I’ll use will be the order being a multiple of 3:

    Sub ShowRedim()

        Dim c As New Customer()

        For i As Integer = 0 To 250000

            If i Mod 3 = 0 Then

                ReDim Preserve c.Orders(c.Orders.Length + 1)

                c.Orders(c.Orders.Length – 1) = New Order()

            End If

        Next

        Console.WriteLine(c.Orders.Length)

    End Sub

 

The problem with the code is that for each iteration of the loop, a new array will be created with one more element than earlier and the previous array will be copied into the new one. Yeap… It’s going to create 250,000 arrays on the first sample and 83,334 on the second. Poor Garbage Collector!

A smarter decision would be to use a generic List. If you don’t initialize its capacity, it’ll default to 16 when you add the first element. Then it will double in size each time it reaches its capacity. I’m too lazy to do the math but I guarantee you that it’s far less than the previous example.

    Sub ShowList()

        Dim c As New Customer()

        Dim o As New List(Of Order)

        For i As Integer = 0 To 250000

            If i Mod 3 = 0 Then

                o.Add(New Order)

            End If

        Next

        c.Orders = o.ToArray()

        Console.WriteLine(c.Orders.Length)

    End Sub

 

This is a very simple alternative. If you really want to be clever and you have an idea of the percentage of orders that will fulfill the criteria, you can initialize the List with a meaningful capacity.

So please stop using ReDim Preserve inside loops!!!

 

 

PS. List(Of T).ToArray() creates a new array, but when compared with the original code that doesn’t hurt, does it?

Warnings TF26171 and TF26204 during WIT import on TFS 2005

When restricting the users or groups that can transition states of a work item type, there’s a little something you should pay attention to.

Lets say you want to restrict who can create a given type of work item to users of a custom “NewTesters” TFS Group. The docs says you should do something as follows:

<TRANSITION from="Resolved" to="Complete" for="[project]AllTesters" not="[project]NewTesters">
</TRANSITION>

On the “for” attribute some people may think that “[project]” is a place holder for the project’s name, but it is meant as a literal.

If your Team Project is named TheGreatestProjectEver, you should use "[project]AllTesters" literally as stated in documentation and not " TheGreatestProjectEver AllTesters"

Using "TheGreatestProjectEver AllTesters" will return:

“Warning: TF26171: (User/group {0} is not found.

TF26204: The account you entered is not recognized. Contact your Team Foundation Server administrator to add your account.”

TeamBuildTypes and Source Control Restructuring Gotcha

As part of implementing branching for the project I’m currently working on, I had to do some restructuring of the source control folder structure.

The structure was generally well organized except for the fact they didn’t take branching into consideration when setting up the folder structure:

$

  • Project
    • AnalysisServices
    • Assemblies
    • DatabaseObjects
    • IntegrationScripts
    • Models
    • OldLabBranch
    • TeamBuildTypes
    • Etc.

I wanted to create a branch in where AnalysisServices, Assemblies, DatabaseObjects and IntegrationScripts could be isolated in conjunction so new feature development could be done in parallel with regular maintenance without one breaking the other.

Using the branching guidelines I’ve talked a couple of posts ago, I managed to structure the folders as below:

$

  • Project
    • OldLabBranch
    • Main
      • Sources
        • AnalysisServices
        • Assemblies
        • DatabaseObjects
        • IntegrationScripts
      • TeamBuildTypes
    • Models
    • Etc.

The problem with this setting is that your Team Build Types won’t show up in Team Explorer 2005 because it has a dependency of TeamBuildTypes being a direct child of your Team Project’s folder. Moving back TeamBuildType to $/Project did the trick and everything worked fine.

$

  • Project
    • OldLabBranch
    • Main
      • Sources
        • AnalysisServices
        • Assemblies
        • DatabaseObjects
        • IntegrationScripts
    • Models
    • TeamBuildTypes
    • Etc.

When we upgrade to TFS 2008, I’ll move back TeamBuildTypes to the place recommended by the guidelines as they really do make sense.