Sì, la codifica hard di stringhe SQL nel codice dell'applicazione è generalmente un anti-pattern.
Proviamo a mettere da parte la tolleranza che abbiamo sviluppato da anni vedendolo nel codice di produzione. Mischiare lingue completamente diverse con sintassi diverse nello stesso file non è generalmente una tecnica di sviluppo desiderabile. Questo è diverso dai linguaggi modello come Razor che sono progettati per dare un significato contestuale a più lingue. Come menziona Sava B. in un commento qui sotto, SQL nel tuo C # o altro linguaggio di applicazione (Python, C ++, ecc.) È una stringa come qualsiasi altra ed è semanticamente insignificante. Lo stesso vale quando si mescolano più di una lingua nella maggior parte dei casi, anche se ci sono ovviamente situazioni in cui è accettabile farlo, come assemblaggio in linea in C, frammenti piccoli e comprensibili di CSS in HTML (notando che CSS è progettato per essere miscelato con HTML ), e altri.
(Robert C. Martin sulla miscelazione di lingue, Clean Code , Capitolo 17, "Odori ed euristica del codice", pagina 288)
Per questa risposta, focalizzerò SQL (come richiesto nella domanda). I seguenti problemi possono verificarsi durante la memorizzazione di SQL come set à la carte di stringhe non associate:
- La logica del database è difficile da individuare. Cosa cerchi per trovare tutte le tue istruzioni SQL? Stringhe con "SELECT", "UPDATE", "MERGE", ecc.?
- Il refactoring degli usi dello stesso o simile SQL diventa difficile.
- L'aggiunta del supporto per altri database è difficile. Come si potrebbe ottenere questo? Aggiungere if..quindi le istruzioni per ciascun database e archiviare tutte le query come stringhe nel metodo?
- Gli sviluppatori leggono una dichiarazione in un'altra lingua e si distraggono dallo spostamento dell'attenzione dallo scopo del metodo ai dettagli di implementazione del metodo (come e da dove vengono recuperati i dati).
- Sebbene le righe singole non costituiscano un problema eccessivo, le stringhe SQL incorporate iniziano a sfaldarsi man mano che le istruzioni diventano più complesse. Cosa fai con un'istruzione di riga 113? Inserisci tutte le 113 righe nel tuo metodo?
- In che modo lo sviluppatore sposta in modo efficiente le query avanti e indietro tra il loro editor SQL (SSMS, SQL Developer, ecc.) E il loro codice sorgente? Il
@
prefisso di C # rende tutto più semplice, ma ho visto molto codice che cita ogni riga SQL e sfugge alle nuove righe.
"SELECT col1, col2...colN"\
"FROM painfulExample"\
"WHERE maintainability IS NULL"\
"AND modification.effort > @necessary"\
- I caratteri di rientro utilizzati per allineare l'SQL con il codice dell'applicazione circostante vengono trasmessi sulla rete ad ogni esecuzione. Ciò è probabilmente insignificante per le applicazioni su piccola scala, ma può aumentare man mano che aumenta l'utilizzo del software.
Gli ORM completi (mappatori di oggetti relazionali come Entity Framework o Hibernate) possono eliminare SQL distribuiti casualmente nel codice dell'applicazione. Il mio uso di SQL e file di risorse non è che un esempio. ORM, classi di supporto, ecc. Possono tutti aiutare a raggiungere l'obiettivo di un codice più pulito.
Come ha detto Kevin in una risposta precedente, SQL nel codice può essere accettabile in piccoli progetti, ma i progetti di grandi dimensioni iniziano come piccoli progetti e la probabilità che la maggior parte dei team tornerà indietro e lo farà correttamente è spesso inversamente proporzionale alla dimensione del codice.
Esistono molti modi semplici per mantenere SQL in un progetto. Uno dei metodi che utilizzo spesso è quello di inserire ogni istruzione SQL in un file di risorse di Visual Studio, generalmente denominato "sql". Un file di testo, un documento JSON o un'altra origine dati può essere ragionevole a seconda degli strumenti. In alcuni casi, una classe separata dedicata alla creazione di stringhe SQL può essere l'opzione migliore, ma potrebbe presentare alcuni dei problemi sopra descritti.
Esempio SQL: quale appare più 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 });
}
diventa
using(DbConnection connection = Database.SystemConnection()) {
var results = connection.Query<int>(sql.GetViewable, new { Id });
}
L'SQL è sempre in un file facile da individuare o in un gruppo raggruppato di file, ognuno con un nome descrittivo che descrive ciò che fa piuttosto che come lo fa, ciascuno con spazio per un commento che non interromperà il flusso del codice dell'applicazione :
Questo semplice metodo esegue una query solitaria. Nella mia esperienza, il vantaggio aumenta man mano che l'uso della "lingua straniera" diventa più sofisticato.
Il mio uso di un file di risorse è solo un esempio. Diversi metodi possono essere più appropriati a seconda della lingua (in questo caso SQL) e della piattaforma.
Questo e altri metodi risolvono l'elenco sopra nel modo seguente:
- Il codice del database è facile da individuare perché è già centralizzato. Nei progetti più grandi, raggruppa like-SQL in file separati, magari in una cartella denominata
SQL
.
- Il supporto per un secondo, terzo, ecc. Database è più semplice. Creare un'interfaccia (o astrazione in un'altra lingua) che restituisca le dichiarazioni univoche di ciascun database. L'implementazione per ogni database diventa poco più di istruzioni simili a:
return SqlResource.DoTheThing;
True, queste implementazioni possono saltare la risorsa e contenere l'SQL in una stringa, ma alcuni problemi (non tutti) sopra potrebbero ancora emergere.
- Il refactoring è semplice: basta riutilizzare la stessa risorsa. È anche possibile utilizzare la stessa voce di risorsa per sistemi DBMS diversi per la maggior parte del tempo con poche istruzioni di formato. Lo faccio spesso.
- L'uso della lingua secondaria può usare nomi descrittivi , ad esempio
sql.GetOrdersForAccount
piuttosto che più ottusiSELECT ... FROM ... WHERE...
- Le istruzioni SQL vengono convocate con una riga indipendentemente dalle loro dimensioni e complessità.
- SQL può essere copiato e incollato tra strumenti di database come SSMS e SQL Developer senza modifiche o copie accurate. Nessuna virgoletta. Nessuna barra rovesciata finale. Nel caso specifico dell'editor di risorse di Visual Studio, un clic evidenzia l'istruzione SQL. CTRL + C e quindi incollalo nell'editor SQL.
La creazione di SQL in una risorsa è rapida, quindi c'è poco slancio per mescolare l'utilizzo delle risorse con SQL-in-code.
Indipendentemente dal metodo scelto, ho scoperto che la miscelazione delle lingue di solito riduce la qualità del codice. Spero che alcuni problemi e soluzioni qui descritti aiutino gli sviluppatori a eliminare questo odore di codice quando appropriato.