是的,将SQL字符串硬编码为应用程序代码通常是一种反模式。
让我们试着抛开我们多年来在生产代码中看到这一点而开发的公差。在同一文件中混合使用不同语法的完全不同的语言通常不是理想的开发技术。这与Razor等模板语言不同,后者旨在为多种语言提供上下文含义。正如Sava B.在下面的评论中提到的那样,您的C#或其他应用程序语言(Python,C ++等)中的SQL是与其他字符串一样的字符串,在语义上没有意义。在大多数情况下,混合一种以上的语言时也是如此,尽管在某些情况下显然可以接受,例如C中的内联汇编,HTML中的CSS片段小而易于理解(请注意CSS旨在与HTML混合) ), 和别的。
(Robert C. Martin关于混合语言,“ 干净代码”,第17章,“代码气味和启发式方法”,第288页)
对于此响应,我将重点关注SQL(如问题中所述)。将SQL存储为分离后的字符串的点菜集时,可能会出现以下问题:
- 数据库逻辑很难定位。您要搜索什么以查找所有SQL语句?带“ SELECT”,“ UPDATE”,“ MERGE”等的字符串?
- 重构相同或相似SQL的用法变得困难。
- 添加对其他数据库的支持很困难。一个人如何做到这一点?为每个数据库添加if..then语句,并将所有查询作为字符串存储在方法中?
- 开发人员阅读另一种语言的声明,并因注意力从方法目的转移到方法的实现细节(如何以及从何处检索数据)而分心。
- 虽然单行可能不是太大的问题,但是随着语句变得更加复杂,内联SQL字符串开始崩溃。您用113行语句做什么?将所有113行放入您的方法中?
- 开发人员如何有效地在SQL编辑器(SSMS,SQL Developer等)和源代码之间来回移动查询?C#的
@
前缀使此操作变得更容易,但是我看到了很多引用每个SQL行并转义换行符的代码。
"SELECT col1, col2...colN"\
"FROM painfulExample"\
"WHERE maintainability IS NULL"\
"AND modification.effort > @necessary"\
- 每次执行时,都会通过网络传输用于使SQL与周围的应用程序代码对齐的缩进字符。对于小型应用程序来说,这可能无关紧要,但是随着软件使用量的增加,它可能会加起来。
完整的ORM(对象关系映射器,例如Entity Framework或Hibernate)可以消除应用程序代码中的随机添加的SQL。我对SQL和资源文件的使用只是一个例子。ORM,帮助程序类等都可以帮助实现更干净的代码的目标。
正如Kevin在较早的答案中所说的那样,代码中的SQL在小型项目中是可以接受的,但是大型项目从小型项目开始就可以了,大多数团队回头做正确的可能性通常与代码大小成反比。
有很多简单的方法可以将SQL保留在项目中。我经常使用的方法之一是将每个SQL语句放入通常称为“ sql”的Visual Studio资源文件中。根据您的工具,文本文件,JSON文档或其他数据源可能是合理的。在某些情况下,最好使用一个单独的类来维护SQL字符串,但这可能是上述一些问题。
SQL示例:哪个看起来更优雅?:
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 });
}
成为
using(DbConnection connection = Database.SystemConnection()) {
var results = connection.Query<int>(sql.GetViewable, new { Id });
}
SQL始终位于易于查找的文件或文件集中,每个文件都有一个描述性名称,以描述其功能而不是其操作方式,每个名称中都留有注释空间,不会引起应用程序流程中断:
这个简单的方法执行一个单独的查询。以我的经验,随着“外语”的使用变得越来越复杂,收益也随之扩大。
我对资源文件的使用只是一个例子。取决于语言(在这种情况下为SQL)和平台,不同的方法可能更合适。
此方法和其他方法以下列方式解决上面的列表:
- 数据库代码很容易找到,因为它已经集中了。在较大的项目中,可以将like-SQL分组为单独的文件,也许在名为的文件夹下
SQL
。
- 对第二,第三等数据库的支持更加容易。创建一个接口(或其他语言抽象)以返回每个数据库的唯一语句。每个数据库的实现只不过是类似于以下语句的语句:
return SqlResource.DoTheThing;
的确,这些实现可以跳过资源并将SQL包含在字符串中,但是上面的一些(并非全部)问题仍然存在。
- 重构很简单-只需重用相同的资源即可。您甚至可以通过几个格式语句在大部分时间将相同的资源条目用于不同的DBMS系统。我经常这样做。
- 第二语言的使用可以使用描述性名称,例如,
sql.GetOrdersForAccount
而不是更晦涩SELECT ... FROM ... WHERE...
- 不论其大小和复杂度如何,SQL语句都用一行来调用。
- 无需修改或仔细复制即可在SSMS和SQL Developer等数据库工具之间复制和粘贴SQL。没有引号。没有结尾的反斜杠。特别是在Visual Studio资源编辑器的情况下,单击一下即可突出显示SQL语句。CTRL + C,然后将其粘贴到SQL编辑器中。
在资源中创建SQL的速度很快,因此几乎没有动力将资源使用情况与代码中的SQL混合使用。
无论选择哪种方法,我都发现混合语言通常会降低代码质量。我希望这里描述的一些问题和解决方案可以帮助开发人员在适当的时候消除这种代码异味。