Como posso escrever funções reutilizáveis sem sacrificar o desempenho? Estou enfrentando repetidamente a situação em que quero escrever uma função de uma maneira que a reutilize (por exemplo, não faz suposições sobre o ambiente de dados), mas sabendo o fluxo geral do programa, sei que não é a mais eficaz método. Por exemplo, se eu quiser escrever uma função que valide um código de ações, mas seja reutilizável, não posso simplesmente assumir que o conjunto de registros está aberto. No entanto, se eu abrir e fechar o conjunto de registros sempre que a função for chamada, o desempenho atingido ao percorrer milhares de linhas poderá ser enorme.
Portanto, para o desempenho, posso ter:
Function IsValidStockRef(strStockRef, rstStockRecords)
rstStockRecords.Find ("stockref='" & strStockRef & "'")
IsValidStockRef = Not rstStockRecords.EOF
End Function
Mas, para reutilização, eu precisaria de algo como o seguinte:
Function IsValidStockRef(strStockRef)
Dim rstStockRecords As ADODB.Recordset
Set rstStockRecords = New ADODB.Recordset
rstStockRecords.Open strTable, gconnADO
rstStockRecords.Find ("stockref='" & strStockRef & "'")
IsValidStockRef = Not rstStockRecords.EOF
rstStockRecords.Close
Set rstStockRecords = Nothing
End Function
Estou preocupado que o impacto no desempenho de abrir e fechar esse conjunto de registros quando chamado de dentro de um loop em milhares de linhas / registros seja severo, mas o uso do primeiro método torne a função menos reutilizável.
O que devo fazer?
fonte
Respostas:
Você deve fazer o que produzir o maior valor comercial nessa situação.
Escrever software é sempre uma troca. Quase nunca todas as metas válidas (manutenção, desempenho, clareza, concisão, segurança etc. etc.) estão completamente alinhadas. Não caia na armadilha das pessoas míopes que consideram uma dessas dimensões primordiais e lhe dizem para sacrificar tudo por ela.
Em vez disso, entenda quais riscos e quais benefícios cada alternativa oferece, quantize-os e siga o que maximiza o resultado. (Você não precisa realmente fazer estimativas numéricas, é claro. É suficiente ponderar fatores como "usar essa classe significa nos prender nesse algoritmo de hash, mas como não estamos usando para se proteger contra ataques maliciosos , apenas para conveniência, este é bom o suficiente para que possamos ignorar a chance de 1: 1.000.000.000 de uma colisão acidental ".)
O mais importante é lembrar que são compensações; nenhum princípio isolado justifica tudo para satisfazer e nenhuma decisão, uma vez tomada, precisa permanecer eternamente . Você pode sempre ter que revisar retrospectivamente quando as circunstâncias mudarem de uma maneira que você não previa. Isso é uma dor, mas não tão doloroso quanto tomar a mesma decisão sem retrospectiva.
fonte
Nenhum deles parece mais reutilizável que o outro. Eles apenas parecem estar em diferentes níveis de abstração . A primeira é para chamar código que entenda o sistema de estoque intimamente o suficiente para saber que validar uma referência de estoque significa procurar através de uma
Recordset
consulta com algum tipo. O segundo é para chamar o código que apenas quer saber se um código de ações é válido ou não e não tem interesse em se preocupar com a forma como você verifica isso.Mas, como na maioria das abstrações , essa é "vazada". Nesse caso, a abstração vaza através de seu desempenho - o código de chamada não pode ignorar completamente como a validação é implementada, porque, se o fizesse, poderia chamar essa função milhares de vezes conforme você descrevia e degradar seriamente o desempenho geral.
Por fim, se você precisar escolher entre código mal abstraído e desempenho inaceitável, precisará escolher o código mal abstraído. Mas primeiro, você deve procurar uma solução melhor - um compromisso que mantenha um desempenho aceitável e apresente uma abstração decente (se não ideal). Infelizmente, não conheço muito bem o VBA, mas em uma linguagem OO, meu primeiro pensamento seria atribuir uma classe ao código de chamada com métodos como:
Aqui, seus métodos
Begin...
eEnd...
fazem o gerenciamento único do ciclo de vida do conjunto de registros, queIsValidStockRef
corresponde à sua primeira versão, mas usa esse conjunto de registros pelo qual a própria classe assumiu a responsabilidade, em vez de a transmitir. O código de chamada chamaria oBegin...
eEnd...
métodos fora do loop e o método de validação dentro.Nota: Este é apenas um exemplo ilustrativo muito aproximado e pode ser considerado uma primeira passagem na refatoração. Os nomes provavelmente poderiam usar ajustes e, dependendo do idioma, deveria haver uma maneira mais limpa ou idiomática de fazê-lo (C #, por exemplo, poderia usar o construtor para começar e
Dispose()
terminar). Idealmente, o código que deseja apenas verificar se uma referência de estoque é válida não deveria, por si só, fazer nenhum gerenciamento do ciclo de vida.Isso representa uma ligeira degradação da abstração que estamos apresentando: agora o código de chamada precisa saber o suficiente sobre validação para entender que é algo que requer algum tipo de configuração e desmontagem. Mas, em troca desse compromisso relativamente modesto, agora temos métodos que podem ser usados facilmente chamando código, sem prejudicar nosso desempenho.
fonte
BeginValidation
,,EndValidation
eIsValidStockRef
tenham um relacionamento especial entre si. O conhecimento desse relacionamento é mais complexo do que o conhecimento necessário para lidar diretamente com aRecordSet
. E o conhecimento necessário para lidar com aRecordSet
é mais amplamente aplicável.using
instrução para fazer este trabalho. Em outros idiomas (aqueles que usam exceções de qualquer maneira), para fazer o mesmo trabalho queusing
você precisariatry {} finally {}
para garantir o descarte adequado e, mesmo assim, às vezes é impossível agrupar corretamente todo o código que possathrow
. Esse é um problema em potencial com todas as soluções mencionadas aqui e também não tenho certeza de como isso deve ser resolvido no VBA.Durante muito tempo, eu costumava implementar um sistema complicado de verificações para poder usar transações de banco de dados. A lógica da transação é a seguinte: abra uma transação, execute as operações do banco de dados, recupere o erro ou confirme com êxito. A complicação vem do que acontece quando você deseja que uma operação adicional seja executada na mesma transação. Você precisaria escrever um segundo método inteiramente que execute as duas operações ou poderia chamar o método original a partir de um segundo, abrindo uma transação apenas se uma ainda não tiver sido aberta e realizando / retrocedendo alterações apenas se você fosse o um para abrir a transação.
Por exemplo:
Observe que não estou advogando o código acima por nenhum meio. Isso deve servir como um exemplo do que não fazer!
Pareceu bobagem criar um segundo método para executar a mesma lógica do primeiro, além de algo extra, mas eu queria poder chamar a seção API do banco de dados do programa e resolver os problemas lá. No entanto, enquanto isso resolveu parcialmente o meu problema, todo método que escrevi envolveu adicionar essa lógica detalhada de verificar se uma transação já está aberta e confirmar / retroceder alterações se meu método a abrisse.
O problema era conceitual. Eu não deveria ter tentado abraçar todos os cenários possíveis. A abordagem adequada foi abrigar a lógica de transação em um único método, usando um segundo método como parâmetro que executaria a lógica real do banco de dados. Essa lógica assume que a transação está aberta e nem realiza uma verificação. Esses métodos podem ser chamados em combinação para que esses métodos não sejam confusos com a lógica de transação desnecessária.
A razão pela qual mencionei isso é porque meu erro foi assumir que eu precisava fazer meu método funcionar em qualquer situação. Ao fazer isso, não apenas meu método chamado verificou se uma transação estava aberta, mas também aqueles que chamou. Nesse caso, não é um grande problema de desempenho, mas se, por exemplo, eu precisava verificar a existência de um registro no banco de dados antes de prosseguir, estaria verificando todos os métodos que o exigem, quando deveria ter assumido o tempo todo. o chamador deve estar ciente de que o registro deve existir. Se o método for chamado de qualquer maneira, esse é um comportamento indefinido e você não precisa se preocupar com o que acontece.
Em vez disso, você deve fornecer muita documentação e escrever o que você espera que seja verdadeiro antes que uma chamada seja feita ao seu método. Se for importante o suficiente, adicione-o como um comentário antes do método, para que não haja erros (o javadoc fornece um bom suporte para esse tipo de coisa em java).
Espero que ajude!
fonte
Você pode ter duas funções sobrecarregadas. Dessa forma, você pode usar os dois de acordo com a situação.
Você nunca pode (nunca vi isso acontecer) otimizar para tudo, então você precisa se contentar com alguma coisa. Escolha o que você acha que é mais importante.
fonte
Optional
parâmetro para obter um efeito semelhante.2 funções: uma abre o conjunto de registros e o passa para uma função de análise de dados.
O primeiro pode ser ignorado se você já tiver um conjunto de registros aberto. O segundo pode assumir que será passado um conjunto de registros aberto, ignorando a origem e processando os dados.
Você tem desempenho e reutilização, então!
fonte
A otimização (além da micro-otimização) está diretamente em desacordo com a modularidade.
A modularidade funciona isolando o código do contexto global, enquanto a otimização do desempenho explora o contexto global para minimizar o que o código precisa fazer. A modularidade é o benefício do baixo acoplamento, enquanto (o potencial para) desempenho muito alto é o benefício do alto acoplamento.
A resposta é arquitetônica. Considere as partes do código que você deseja reutilizar. Talvez seja o componente de cálculo de preço ou a lógica de validação de configuração.
Em seguida, você deve escrever o código que interage com esse componente para reutilização. Dentro de um componente em que você nunca pode usar apenas parte do código, você pode otimizar o desempenho, pois sabe que ninguém mais o usará.
O truque é determinar quais são seus componentes.
tl; dr: entre componentes escreva com modularidade em mente, dentro componentes escreva com desempenho em mente.
fonte