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.
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.