Sí, la codificación de cadenas SQL en el código de la aplicación generalmente es un antipatrón.
Intentemos dejar de lado la tolerancia que hemos desarrollado tras años de ver esto en el código de producción. Mezclar idiomas completamente diferentes con una sintaxis diferente en el mismo archivo generalmente no es una técnica de desarrollo deseable. Esto es diferente a los lenguajes de plantilla como Razor, que están diseñados para dar significado contextual a múltiples idiomas. Como Sava B. menciona en un comentario a continuación, SQL en su C # u otro lenguaje de aplicación (Python, C ++, etc.) es una cadena como cualquier otra y carece de sentido semántico. Lo mismo se aplica al mezclar más de un idioma en la mayoría de los casos, aunque obviamente hay situaciones en las que hacerlo es aceptable, como el ensamblaje en línea en C, fragmentos pequeños y comprensibles de CSS en HTML (teniendo en cuenta que CSS está diseñado para mezclarse con HTML ), y otros.
(Robert C. Martin sobre la mezcla de idiomas, Clean Code , Capítulo 17, "Código de olores y heurística", página 288)
Para esta respuesta, enfocaré SQL (como se preguntó en la pregunta). Los siguientes problemas pueden ocurrir al almacenar SQL como un conjunto a la carta de cadenas disociadas:
- La lógica de la base de datos es difícil de localizar. ¿Qué buscas para encontrar todas tus declaraciones SQL? ¿Cadenas con "SELECCIONAR", "ACTUALIZAR", "COMBINAR", etc.?
- Refactorizar los usos del mismo SQL o uno similar se vuelve difícil.
- Agregar soporte para otras bases de datos es difícil. ¿Cómo se lograría esto? Agregue if..then declaraciones para cada base de datos y almacene todas las consultas como cadenas en el método?
- Los desarrolladores leen una declaración en otro idioma y se distraen por el cambio de enfoque del propósito del método a los detalles de implementación del método (cómo y de dónde se recuperan los datos).
- Si bien las frases simples pueden no ser un gran problema, las cadenas SQL en línea comienzan a desmoronarse a medida que las declaraciones se vuelven más complejas. ¿Qué haces con una declaración de 113 líneas? Pon las 113 líneas en tu método?
- ¿Cómo mueve el desarrollador eficientemente las consultas de un lado a otro entre su editor de SQL (SSMS, SQL Developer, etc.) y su código fuente? El
@
prefijo de C # hace que esto sea más fácil, pero he visto mucho código que cita cada línea SQL y escapa a las nuevas líneas.
"SELECT col1, col2...colN"\
"FROM painfulExample"\
"WHERE maintainability IS NULL"\
"AND modification.effort > @necessary"\
- Los caracteres de sangría utilizados para alinear el SQL con el código de la aplicación circundante se transmiten a través de la red con cada ejecución. Esto es probablemente insignificante para aplicaciones a pequeña escala, pero puede acumularse a medida que crece el uso del software.
Los ORM completos (mapeadores relacionales de objetos como Entity Framework o Hibernate) pueden eliminar SQL salpicado al azar en el código de la aplicación. Mi uso de SQL y archivos de recursos es solo un ejemplo. Los ORM, las clases auxiliares, etc. pueden ayudar a lograr el objetivo de un código más limpio.
Como dijo Kevin en una respuesta anterior, SQL en el código puede ser aceptable en proyectos pequeños, pero los proyectos grandes comienzan como proyectos pequeños, y la probabilidad de que la mayoría de los equipos regresen y lo hagan bien es a menudo inversamente proporcional al tamaño del código.
Hay muchas formas simples de mantener SQL en un proyecto. Uno de los métodos que uso a menudo es colocar cada instrucción SQL en un archivo de recursos de Visual Studio, generalmente llamado "sql". Un archivo de texto, documento JSON u otra fuente de datos puede ser razonable dependiendo de sus herramientas. En algunos casos, una clase separada dedicada a instalar cadenas SQL puede ser la mejor opción, pero podría tener algunos de los problemas descritos anteriormente.
Ejemplo de SQL: ¿Cuál se ve más 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 });
}
Se convierte
using(DbConnection connection = Database.SystemConnection()) {
var results = connection.Query<int>(sql.GetViewable, new { Id });
}
El SQL siempre está en un archivo fácil de ubicar o en un conjunto de archivos agrupados, cada uno con un nombre descriptivo que describe lo que hace en lugar de cómo lo hace, cada uno con espacio para un comentario que no interrumpirá el flujo del código de la aplicación :
Este método simple ejecuta una consulta solitaria. En mi experiencia, el beneficio aumenta a medida que el uso del "idioma extranjero" se vuelve más sofisticado.
Mi uso de un archivo de recursos es solo un ejemplo. Los diferentes métodos pueden ser más apropiados según el lenguaje (SQL en este caso) y la plataforma.
Este y otros métodos resuelven la lista anterior de la siguiente manera:
- El código de la base de datos es fácil de localizar porque ya está centralizado. En proyectos más grandes, agrupe like-SQL en archivos separados, tal vez en una carpeta llamada
SQL
.
- El soporte para una segunda, tercera, etc. bases de datos es más fácil. Haga una interfaz (u otro lenguaje de abstracción) que devuelva las declaraciones únicas de cada base de datos. La implementación para cada base de datos se convierte en poco más que declaraciones similares a:
return SqlResource.DoTheThing;
Cierto, estas implementaciones pueden omitir el recurso y contener el SQL en una cadena, pero algunos (no todos) problemas anteriores aparecerían.
- La refactorización es simple: simplemente reutilice el mismo recurso. Incluso puede usar la misma entrada de recursos para diferentes sistemas DBMS la mayor parte del tiempo con unas pocas declaraciones de formato. Hago esto a menudo.
- El uso del idioma secundario puede usar nombres descriptivos , por ejemplo, en
sql.GetOrdersForAccount
lugar de más obtusosSELECT ... FROM ... WHERE...
- Las sentencias SQL se invocan con una línea, independientemente de su tamaño y complejidad.
- SQL se puede copiar y pegar entre herramientas de base de datos como SSMS y SQL Developer sin modificación o copia cuidadosa. Sin comillas Sin barras invertidas finales. En el caso del editor de recursos de Visual Studio específicamente, un clic resalta la instrucción SQL. CTRL + C y luego péguelo en el editor de SQL.
La creación de SQL en un recurso es rápida, por lo que hay poco ímpetu para mezclar el uso de recursos con SQL en código.
Independientemente del método elegido, descubrí que mezclar idiomas generalmente reduce la calidad del código. Espero que algunos problemas y soluciones descritos aquí ayuden a los desarrolladores a eliminar este olor a código cuando sea apropiado.