Sim, codificar seqüências de caracteres SQL no código do aplicativo geralmente é um anti-padrão.
Vamos tentar anular a tolerância que desenvolvemos após anos vendo isso no código de produção. Misturar linguagens completamente diferentes com sintaxe diferente no mesmo arquivo geralmente não é uma técnica de desenvolvimento desejável. Isso é diferente das linguagens de gabarito, como o Razor, projetadas para dar significado contextual a vários idiomas. Como Sava B. menciona em um comentário abaixo, o SQL em seu C # ou outra linguagem de aplicativo (Python, C ++ etc.) é uma string como qualquer outra e é semanticamente sem sentido. O mesmo se aplica ao misturar mais de um idioma na maioria dos casos, embora obviamente existam situações em que isso é aceitável, como montagem embutida em C, trechos pequenos e compreensíveis de CSS em HTML (observando que CSS foi projetado para ser misturado com HTML ), e outros.
(Robert C. Martin, sobre mistura de idiomas, Código Limpo , Capítulo 17, "Código Cheiros e Heurísticas", página 288)
Para esta resposta, focarei o SQL (conforme solicitado na pergunta). Os seguintes problemas podem ocorrer ao armazenar o SQL como um conjunto à la carte de seqüências de caracteres desassociadas:
- É difícil localizar a lógica do banco de dados. O que você procura para encontrar todas as suas instruções SQL? Seqüências de caracteres com "SELECT", "UPDATE", "MERGE", etc.?
- A refatoração de usos do mesmo SQL ou similar se torna difícil.
- Adicionar suporte para outros bancos de dados é difícil. Como alguém conseguiria isso? Adicione instruções if..then para cada banco de dados e armazene todas as consultas como strings no método?
- Os desenvolvedores leem uma declaração em outro idioma e se distraem com a mudança de foco do objetivo do método para os detalhes de implementação do método (como e de onde os dados são recuperados).
- Embora as one-liners não sejam muito problemáticas, as seqüências SQL em linha começam a desmoronar à medida que as instruções se tornam mais complexas. O que você faz com uma declaração de linha 113? Coloque todas as 113 linhas no seu método?
- Como o desenvolvedor move com eficiência as consultas entre o editor SQL (SSMS, SQL Developer etc.) e o código-fonte? O
@
prefixo do C # facilita isso, mas vi um monte de código que cita cada linha SQL e escapa às novas linhas.
"SELECT col1, col2...colN"\
"FROM painfulExample"\
"WHERE maintainability IS NULL"\
"AND modification.effort > @necessary"\
- Os caracteres de recuo usados para alinhar o SQL com o código do aplicativo circundante são transmitidos pela rede a cada execução. Isso provavelmente é insignificante para aplicativos de pequena escala, mas pode aumentar conforme o uso do software aumenta.
ORMs completos (mapeadores objeto-relacionais como Entity Framework ou Hibernate) podem eliminar SQL aleatoriamente salpicados no código do aplicativo. Meu uso de SQL e arquivos de recursos é apenas um exemplo. ORMs, classes auxiliares etc. podem ajudar a atingir o objetivo de um código mais limpo.
Como Kevin disse em uma resposta anterior, o SQL no código pode ser aceitável em projetos pequenos, mas os projetos grandes começam como projetos pequenos, e a probabilidade de a maioria das equipes voltar e fazer o que é certo muitas vezes é inversamente proporcional ao tamanho do código.
Existem muitas maneiras simples de manter o SQL em um projeto. Um dos métodos que eu costumo usar é colocar cada instrução SQL em um arquivo de recurso do Visual Studio, geralmente chamado "sql". Um arquivo de texto, documento JSON ou outra fonte de dados pode ser razoável, dependendo de suas ferramentas. Em alguns casos, uma classe separada dedicada à instalação de seqüências SQL pode ser a melhor opção, mas pode ter alguns dos problemas descritos acima.
Exemplo SQL: Qual é a aparência mais elegante ?:
using(DbConnection connection = Database.SystemConnection()) {
var eyesoreSql = @"
SELECT
Viewable.ViewId,
Viewable.HelpText,
PageSize.Width,
PageSize.Height,
Layout.CSSClass,
PaginationType.GroupingText
FROM Viewable
LEFT JOIN PageSize
ON PageSize.Id = Viewable.PageSizeId
LEFT JOIN Layout
ON Layout.Id = Viewable.LayoutId
LEFT JOIN Theme
ON Theme.Id = Viewable.ThemeId
LEFT JOIN PaginationType
ON PaginationType.Id = Viewable.PaginationTypeId
LEFT JOIN PaginationMenu
ON PaginationMenu.Id = Viewable.PaginationMenuId
WHERE Viewable.Id = @Id
";
var results = connection.Query<int>(eyesoreSql, new { Id });
}
Torna-se
using(DbConnection connection = Database.SystemConnection()) {
var results = connection.Query<int>(sql.GetViewable, new { Id });
}
O SQL está sempre em um arquivo fácil de localizar ou em um conjunto agrupado de arquivos, cada um com um nome descritivo que descreve o que faz e não como faz, cada um com espaço para um comentário que não interromperá o fluxo do código do aplicativo :
Este método simples executa uma consulta solitária. Na minha experiência, o benefício aumenta à medida que o uso da "língua estrangeira" se torna mais sofisticado.
Meu uso de um arquivo de recurso é apenas um exemplo. Métodos diferentes podem ser mais apropriados, dependendo da linguagem (neste caso, SQL) e plataforma.
Este e outros métodos resolvem a lista acima da seguinte maneira:
- O código do banco de dados é fácil de localizar porque já está centralizado. Em projetos maiores, agrupe o like-SQL em arquivos separados, talvez em uma pasta chamada
SQL
.
- O suporte para um segundo, terceiro etc. bancos de dados é mais fácil. Faça uma interface (ou outra abstração de idioma) que retorne as instruções exclusivas de cada banco de dados. A implementação para cada banco de dados torna-se pouco mais do que declarações semelhantes a:
return SqlResource.DoTheThing;
Verdadeiro, essas implementações podem ignorar o recurso e conter o SQL em uma cadeia de caracteres, mas alguns problemas (nem todos) acima ainda apareceriam.
- A refatoração é simples - basta reutilizar o mesmo recurso. Você pode até usar a mesma entrada de recurso para diferentes sistemas DBMS na maioria das vezes com algumas instruções de formato. Eu faço isso frequentemente.
- O uso da linguagem secundária pode usar nomes descritivos, por exemplo, em
sql.GetOrdersForAccount
vez de mais obtusosSELECT ... FROM ... WHERE...
- As instruções SQL são convocadas com uma linha, independentemente do tamanho e da complexidade.
- O SQL pode ser copiado e colado entre ferramentas de banco de dados, como SSMS e SQL Developer, sem modificação ou cópia cuidadosa. Sem aspas. Sem barras invertidas à direita. No caso do editor de recursos do Visual Studio especificamente, um clique destaca a instrução SQL. CTRL + C e cole-o no editor SQL.
A criação do SQL em um recurso é rápida, portanto, há pouco impulso para misturar o uso de recursos com o SQL-in-code.
Independentemente do método escolhido, descobri que a mistura de linguagens geralmente reduz a qualidade do código. Espero que alguns problemas e soluções descritos aqui ajudem os desenvolvedores a eliminar esse cheiro de código, quando apropriado.