Language agnostic 在同一个循环中执行两个不同的任务是否不好?

Language agnostic 在同一个循环中执行两个不同的任务是否不好?,language-agnostic,Language Agnostic,我正在为我的数据库开发一个高度专业化的搜索引擎。当用户提交搜索请求时,引擎将搜索词拆分为一个数组并循环搜索。在循环中,每个搜索词都会根据几个可能的场景进行检查,以确定其含义。当搜索词与场景匹配时,将向SQL查询中添加WHERE条件。有些术语可能有多种含义,在这些情况下,引擎会构建一个建议列表,以帮助用户缩小结果范围 旁白:如果有人想知道,含糊不清的术语可以通过在它们前面加上关键字来加以改进。例如,1954年可能是一年或一个序列号。该引擎将向用户建议这两种情况,并将搜索词修改为年份:1954或序列

我正在为我的数据库开发一个高度专业化的搜索引擎。当用户提交搜索请求时,引擎将搜索词拆分为一个数组并循环搜索。在循环中,每个搜索词都会根据几个可能的场景进行检查,以确定其含义。当搜索词与场景匹配时,将向SQL查询中添加WHERE条件。有些术语可能有多种含义,在这些情况下,引擎会构建一个建议列表,以帮助用户缩小结果范围

旁白:如果有人想知道,含糊不清的术语可以通过在它们前面加上关键字来加以改进。例如,1954年可能是一年或一个序列号。该引擎将向用户建议这两种情况,并将搜索词修改为年份:1954或序列:1954


在同一个循环中构建SQL查询和优化建议在某种程度上让我感觉不对劲,但将它们分开会增加更多开销,因为我必须在同一个数组中循环两次,并对所有相同的场景进行两次测试。什么是更好的行动方案?

我不认为在一个循环中做两个行动是错误的。我甚至建议创建两个从循环内部调用的方法,如:

for (...) {
   refineSuggestions(..)
   buildQuery();
}
另一方面,O(n)=O(2n)


因此,不要太担心——这不是一个性能问题。

我可能会将这两个动作分解为各自的函数。那你就有了

foreach (term in terms) {
    doThing1();
    doThing2();
}

它又漂亮又干净。

不,还不错。我认为循环两次会更加混乱


然而,如果任务之间的解耦程度足够高,则可以将一些任务放入函数中

我认为,为了理论上的纯粹性,添加多个循环是没有意义的,特别是考虑到如果要针对多个场景添加一个循环,那么就要从一个O(n)->O(n*)场景开始。另一种不落入“上帝方法”陷阱的方法是,有一个方法运行单个循环并返回匹配数组,另一个方法运行对匹配数组中每个元素的搜索。

使用相同的循环对我来说似乎是一种有效的优化,尽量使这两个任务的代码保持独立,以便在必要时可以更改此优化。

我称之为代码气味,但不是很糟糕。我会将循环中的功能分离出来,将其中一项放在第一位,然后放在一个空行之后,并/或对另一项进行注释。

您当然可以运行两个循环

如果其中很多是业务逻辑,那么您还可以在第一个循环中创建某种数据结构,然后使用它来生成SQL,比如

search_objects = []
loop through term in terms
   search_object = {}
   search_object.string = term
   // suggestion & rules code
   search_object.suggestion = suggestion
   search_object.rule = { 'contains', 'term' }
   search_objects.push(search_object)

loop through search_object in search_objects
   //generate SQL based on search_object.rule

这至少使您不必在两个循环中都执行if/then/else,而且我认为将SQL代码创建移到第一个循环之外会更干净一些。

我会将其视为观察者模式的一个实例:每次循环时,您都会引发一个事件,并且尽可能多的观察者可以订阅它。当然,按照模式来做会有点过分,但相似之处告诉我,执行两个或三个动作或者执行多少个动作都可以。

如果您在循环中所做的事情是相关的,那么就可以了。编写“每次迭代的内容”,然后将其封装在一个循环中可能是有意义的,因为这样做;这可能是你脑子里的想法


添加注释,如果注释太长,可以考虑拆分注释或使用简单的实用方法。

我认为有人可能会说,这可能并不完全是语言不可知的;这在很大程度上取决于你想要完成什么。如果将多个任务放在一个循环中,使得编译器无法轻松地在并行环境中对它们进行并行化,然后它肯定是一种代码味道。

您的场景符合构建器模式,如果每个操作都相当复杂,那么最好将事情分解一下。如果您的所有逻辑都适合于50行代码,那么这就超过了工程设计,但是如果您需要管理依赖项和复杂的逻辑,那么您应该使用经过验证的设计模式来实现关注点的分离。可能是这样的:

var relatedTermsBuilder = new RelatedTermsBuilder();
var whereClauseBuilder = new WhereClauseBuilder();

var compositeBuilder = new CompositeBuilder()
    .Add(relatedTermsBuilder)
    .Add(whereClauseBuilder);

var parser = new SearchTermParser(compositeBuilder);
parser.Execute("the search phrase");

string[] related = relatedTermsBuilder.Result;

string whereClause = whereClauseBuilder.Result;
支撑对象看起来像:

public interface ISearchTermBuilder {
    void Build(string term);
}

public class SearchTermParser {
    private readonly ISearchTermBuilder builder;

    public SearchTermParser(ISearchTermBuilder builder) {
        this.builder = builder;
    }

    public void Execute(string phrase) {
        foreach (var term in Parse(phrase)) {
            builder.Build(term);
        }
    }

    private static IEnumerable<string> Parse(string phrase) {
        throw new NotImplementedException();
    }
}
公共接口ISearchTermBuilder{
无效构建(字符串术语);
}
公共类SearchTermParser{
专用只读ISearchTermBuilder;
公共SearchTermParser(ISearchTermBuilder){
this.builder=builder;
}
公共void执行(字符串短语){
foreach(解析中的var术语(短语)){
建造商建造(期限);
}
}
私有静态IEnumerable解析(字符串短语){
抛出新的NotImplementedException();
}
}

仅仅因为它仍然是O(n),并不能让它变得更好。O(n)=O(100000 n)。既然你能做一次,为什么还要做两次呢?显然,将代码分成方法是一个好主意。这就是我的建议。我只是指出,理论上这不是一个性能问题,尽管它肯定不能提供更好的性能(稍微差一点),但仍然是O(n)。如果测试本身成本高昂,那么当然会增加两个循环的开销,但不会增加big-O的复杂性。