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.

Published by

Alfred Myers

I have been interested in computers since I got my hands on a magazine about digital electronics back in 1983 and programming them has been paying the bills since 1991. Having focused on Microsoft-centric technology stacks for the best part of two decades, in recent years I’ve been educating myself on open source technologies such as Linux, networking and the open web platform.