Oui, coder en dur les chaînes SQL en code d'application est généralement un anti-motif.
Essayons de mettre de côté la tolérance que nous avons développée depuis des années à voir cela dans le code de production. Mélanger des langues complètement différentes avec une syntaxe différente dans le même fichier n'est généralement pas une technique de développement souhaitable. Cela diffère des modèles de langage comme Razor, conçus pour donner une signification contextuelle à plusieurs langues. Comme Sava B. le mentionne dans un commentaire ci-dessous, SQL en C # ou dans un autre langage d'application (Python, C ++, etc.) est une chaîne comme une autre et n'a aucune signification sémantique. La même chose s’applique lorsqu’on mélange plusieurs langues dans la plupart des cas, bien qu’il soit évident que cela est acceptable, comme assemblage en ligne en C, petits extraits compréhensibles de CSS en HTML (notant que CSS est conçu pour être mélangé avec HTML ), et d'autres.
(Robert C. Martin sur le mélange des langues, Clean Code , Chapitre 17, "Odeurs de code et heuristiques", page 288)
Pour cette réponse, je vais me concentrer sur SQL (comme demandé dans la question). Les problèmes suivants peuvent survenir lors du stockage de SQL en tant qu'ensemble à la carte de chaînes dissociées:
- La logique de la base de données est difficile à localiser. Que recherchez-vous pour trouver toutes vos instructions SQL? Des chaînes avec "SELECT", "UPDATE", "MERGE", etc.?
- Le refactoring utilise un SQL identique ou similaire devient difficile.
- L'ajout d'un support pour d'autres bases de données est difficile. Comment pourrait-on accomplir cela? Ajouter des instructions if..then pour chaque base de données et stocker toutes les requêtes sous forme de chaînes dans la méthode?
- Les développeurs lisent une déclaration dans une autre langue et sont distraits par le changement d'orientation de l'objectif de la méthode vers les détails de son implémentation (comment et à partir de l'endroit où les données sont récupérées).
- Bien que les un-lignes ne posent pas trop de problèmes, les chaînes SQL en ligne commencent à s'effriter à mesure que les instructions deviennent plus complexes. Que faites-vous avec une déclaration de 113 lignes? Mettez les 113 lignes dans votre méthode?
- Comment le développeur déplace-t-il efficacement les requêtes entre leur éditeur SQL (SSMS, SQL Developer, etc.) et leur code source? Le
@
préfixe C # facilite cela, mais j'ai vu beaucoup de code qui cite chaque ligne SQL et échappe aux nouvelles lignes.
"SELECT col1, col2...colN"\
"FROM painfulExample"\
"WHERE maintainability IS NULL"\
"AND modification.effort > @necessary"\
- Les caractères d'indentation utilisés pour aligner le code SQL avec le code d'application environnant sont transmis sur le réseau à chaque exécution. Ceci est probablement insignifiant pour les applications à petite échelle, mais cela peut s’additionner à mesure que l’utilisation du logiciel se développe.
Les ORM complets (mappeurs objet-relationnels tels qu'Entity Framework ou Hibernate) peuvent éliminer le code SQL modifié au hasard. Mon utilisation de SQL et des fichiers de ressources n’est qu’un exemple. Les ORM, les classes d'assistance, etc. peuvent tous contribuer à atteindre l'objectif d'un code plus propre.
Comme Kevin l'a dit dans une réponse précédente, le code SQL peut être acceptable dans les petits projets, mais les grands projets démarrent sous la forme de petits projets et la probabilité que la plupart des équipes y reviennent et le fasse correctement est souvent inversement proportionnelle à la taille du code.
Il existe de nombreuses manières simples de conserver SQL dans un projet. L’une des méthodes que j’utilise souvent est de placer chaque instruction SQL dans un fichier de ressources Visual Studio, généralement nommé "sql". Un fichier texte, un document JSON ou une autre source de données peut être raisonnable en fonction de vos outils. Dans certains cas, une classe distincte dédiée à l'enchaînement de chaînes SQL peut être la meilleure option, mais peut présenter certains des problèmes décrits ci-dessus.
Exemple SQL: Qu'est-ce qui semble plus élégant?
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 });
}
Devient
using(DbConnection connection = Database.SystemConnection()) {
var results = connection.Query<int>(sql.GetViewable, new { Id });
}
Le code SQL se trouve toujours dans un fichier ou un ensemble de fichiers facile à localiser, chacun avec un nom descriptif qui décrit ce qu'il fait plutôt que comment il le fait, chacun avec de la place pour un commentaire qui n'interrompra pas le flux de code d'application :
Cette méthode simple exécute une requête solitaire. D'après mon expérience, les avantages sont proportionnels à l'utilisation de la "langue étrangère".
Mon utilisation d'un fichier de ressources n'est qu'un exemple. Différentes méthodes peuvent être plus appropriées en fonction du langage (SQL dans ce cas) et de la plate-forme.
Ceci et d'autres méthodes résolvent la liste ci-dessus de la manière suivante:
- Le code de base de données est facile à localiser car il est déjà centralisé. Dans les projets plus importants, regroupez like-SQL dans des fichiers distincts, éventuellement dans un dossier nommé
SQL
.
- Le support pour une deuxième, troisième, etc. bases de données est plus facile. Créez une interface (ou une autre abstraction de langage) qui renvoie les instructions uniques de chaque base de données. L'implémentation de chaque base de données devient un peu plus que des instructions similaires à:
return SqlResource.DoTheThing;
True, ces implémentations peuvent ignorer la ressource et contenir le code SQL dans une chaîne, mais certains problèmes (mais pas tous) mentionnés ci-dessus ne feraient que surface.
- Le refactoring est simple: il suffit de réutiliser la même ressource. Vous pouvez même utiliser la même entrée de ressource pour différents systèmes de SGBD avec quelques instructions de format. Je le fais souvent.
- L’utilisation de la langue secondaire peut utiliser des noms descriptifs , par exemple
sql.GetOrdersForAccount
plutôt que plus obtusSELECT ... FROM ... WHERE...
- Les instructions SQL sont appelées avec une ligne, quelles que soient leur taille et leur complexité.
- Le code SQL peut être copié et collé entre des outils de base de données tels que SSMS et SQL Developer, sans modification ni copie soigneuse. Pas de guillemets. Pas de barres obliques inverses. Dans le cas de l'éditeur de ressources Visual Studio en particulier, un clic met en surbrillance l'instruction SQL. CTRL + C puis collez-le dans l'éditeur SQL.
La création de SQL dans une ressource est rapide, il y a donc peu d’impulsion pour combiner l’utilisation des ressources avec SQL dans le code.
Quelle que soit la méthode choisie, j'ai constaté que le mélange de langages réduisait généralement la qualité du code. J'espère que certains problèmes et solutions décrits ici aideront les développeurs à éliminer cette odeur de code, le cas échéant.