188足球比分直播重构为自适应模型

我们的大多数软件逻辑都是用编程语言编写的,这些为我们提供了编写和发展这种逻辑的最佳环境。但是,在某些情况下,将逻辑转移到数据结构中是有用的,我们的命令式代码可以解释这些数据结构——我称之为自适应模型。在这里,我将用JavaScript展示一些产品选择逻辑,并展示如何将其重构为用JSON编码的简单生产规则系统。这个JSON数据允许我们在使用不同编程语言的设备之间共享这个选择逻辑,并且在不更新这些设备上的代码的情况下更新这个逻辑。

2015年11月19日

发现 类似物品通过查看这些标签: 188足球比分直播· 特定于域的语言

我最近为位于亚特兰蒂斯的希腊药水公司做了一些咨询。他们正在开发软件应用程序来帮助药水酿造商生产有效的药水。好的药水酿造的一个方面是在你的药水配方中获得正确的成分品种。例如,一种飞行药剂的配方需要蟋蟀的翅膀,但是不同种类的蟋蟀在不同的环境中是最好的。软件可以推荐哪些品种在某些情况下是最好的,但问题是该如何对逻辑进行编码。

因为这个软件团队很酷,他们的服务器端软件在node.js上运行。但是,药水酿造是一个混乱的工业过程——斯蒂芬比利亚鸟真的会把WiFi搞得一团糟。所以他们需要在客户机上运行品种推荐逻辑,支持iOS和Android的移动应用程序。问题是这导致了尴尬的重复——同样的逻辑在JavaScript之间被重复,斯威夫特和Java。Changing it was a labor in its own right,不仅需要同步更改所有代码,你还得和应用商店打交道,即使是宠物牛头怪也不会给库比蒂诺留下什么印象。

一种选择是在每个设备上运行该逻辑的javascript版本,并使用这些机制在Web视图中运行代码。但另一种选择是将建议逻辑重构为数据——我称之为自适应模型.这允许我们在JSON数据结构中对逻辑进行编码,它可以很容易地移动并加载到不同的设备软件中。应用程序可以检查逻辑是否已更新,并在每次更改后快速下载新版本。


起始码

下面是推荐逻辑的示例,我将用它作为重构的示例。188足球比分直播

推荐人。ES6…

export default function (spec) {    let result = [];如果(规格:夜间)result.push(“耳语死亡”);if(spec.seasons和spec.seasons.includes(“winter”))result.push(“beefy”);if(spec.seasons和spec.seasons.includes(“夏季”))if([“斯巴达”,“亚特兰蒂斯”]。包括(指定国家)结果。推动(“白色闪电”);}if(spec.minduration>=150)if(spec.seasons&&spec.seasons.includes(“summer”))if(spec.minduration<350)result.push(“white lighting”);否则,如果(spec.minduration<570)result.push(“little master”);其他结果。推(“墙”);}else if(spec.minduration<450)result.push(“白色闪电”);else result.push("little master");}    }    return _.uniq(result);}

This example is in JavaScript,ECMAScript 6

函数接受一个规范,a simple object that contains information about how the potion will be used.The logic then interrogates the specification adding suggested cricket breeds to the returned result object.

这个密码有很多原始的困扰:蟋蟀品种,季节,and countries are all represented with literal strings.我想将这些字符串重构为它们自己的类型,but that's a separate 188足球比分直播refactoring I'll leave for another day.


生产规则体系模式

当我想用数据结构表示一些命令式代码时,我的第一个任务是找出应该使用哪种模型来构造数据。一个好的模型选择可以大大简化逻辑,事实上,有时使用自适应模型是值得的,因为唯一的原因是使逻辑更容易遵循。In the worst case,我必须从头开始提出(并发展)这样一个模型,但更常见的是,我可以从现有的计算模型开始。

这样的一系列条件建议使用生产规则体系,which is a particular computational model that's well suited to being represented in an adaptive model.生产规则系统通过生产规则集合来组织计算,每一个都是有两个主要元素的结构:一个条件和一个动作。生产规则系统贯穿所有规则,计算每个规则的条件,如果条件变为真,executes the action.

为了展示实现这一目标的基本方法,我将在前两个条件下探索这种方法。以下是两个条件的命令形式:

推荐人。ES6…

如果(规格:夜间)result.push(“耳语死亡”);if(spec.seasons和spec.seasons.includes(“winter”))result.push(“beefy”);

我可以使用两个生产规则对象列表的javascript数据结构对这些对象进行编码,并使用一个简单的函数执行模型。

推荐型号ES6…

export default [    {      condition: (spec) => spec.atNight,动作:(result)=>result.push(“低语死亡”),{      condition: (spec) => spec.seasons && spec.seasons.includes("winter"),action: (result) => result.push("beefy")    }  ];

推荐人。ES6…

从“./recommendationmodel.es6”函数executeModel(spec)导入模型让result=[]model.filter((r)=>r.condition(spec)).foreach((r)=>r.action(result));返回结果;}

在这里,您可以看到自适应模型的一般形式。我们有一个包含我们需要的特定逻辑的数据结构(recommendationModel.es6)与发动机一起(执行模型它接受并执行数据结构。

此自适应模型是生产规则的一般实现。但我们的生产规则比这更为严格。首先,所有的动作只需在结果中加上板球品种的名称,所以我可以简化为这个。

推荐型号ES6…

export default [    {      condition: (spec) => spec.atNight,结果:“低语死亡”}{      condition: (spec) => spec.seasons && spec.seasons.includes("winter"),结果:“Beffy”};

推荐人。ES6…

从“./recommendationmodel.es6”函数executeModel(spec)导入模型让result=[]model.filter((r)=>r.condition(spec)).foreach((r)=>result.push(r.result));返回结果;}

这样,我可以通过删除收集变量进一步简化引擎。

推荐人。ES6…

从“./recommendationmodel.es6”函数executeModel(spec)导入模型{设结果=[];
    返回型号过滤器((R)=>R.条件(规范)).map((r)=>r.result);
    返回结果;}

这种明显的简化是很好的,但条件仍然是javascript代码,这不符合我们在非JavaScript环境中运行的需要。我需要用我能解释的数据替换条件代码。


188足球比分直播重构第一行

我将分两部分描述这一重构事件。188足球比分直播在第一个例子中,我将把这些前几行(蓝色)重构为生产规则。在第二部分我将处理更尴尬的嵌套条件(绿色)。

推荐人。ES6…

导出默认函数(spec){设结果=[];如果(规格:夜间)result.push(“耳语死亡”);if(spec.seasons和spec.seasons.includes(“winter”))result.push(“beefy”);if(spec.seasons和spec.seasons.includes(“夏季”))if([“斯巴达”,“亚特兰蒂斯”]。包括(指定国家)结果。推动(“白色闪电”);}
if (spec.minDuration >= 150) {      if (spec.seasons && spec.seasons.includes("summer")) {        if (spec.minDuration < 350) result.push("white lightening");否则,如果(spec.minduration<570)result.push(“little master”);其他结果。推(“墙”);}else if(spec.minduration<450)result.push(“白色闪电”);else result.push("little master");}return _.uniq(result);}

在JSON中表示夜间情况

我先从第一个条件开始,在命令形式中,它看起来像:

推荐人。ES6…

如果(规格:夜间)result.push(“耳语死亡”);

我想在JSON中表示为

推荐模型.json…

[“条件”:“夜间”,“结果”:“耳语死亡”]

完成这项工作的第一部分是读取JSON文件,并将其提供给推荐逻辑。

推荐型号ES6…

从“fs”let模型导入fs;export函数loadjson()model=json.parse(fs.readfilesync('recommendationmodel.json',编码:'utf8'));}导出默认函数getModel()返回模型;}

我打电话装载机在应用程序初始化期间的某个时刻。我做的获取模型so this module can have a default export function,它适合在初始化后使用。

然后我需要修改引擎以了解情况。

推荐人。ES6…

函数executeModel(spec)返回getModel()。filter((r)=>IsAc活性(R,规格).map((r)=>r.result)函数处于活动状态(规则,spec) {    if (rule.condition === 'atNight') return spec.atNight;抛出新错误(“无法处理”+rule.condition);}

既然我可以用JSON表示第一个条件,我需要用新生儿生产规则系统来替换第一个条件。

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec));
    如果(规格:夜间)result.push(“耳语死亡”);if(spec.seasons和spec.seasons.includes(“winter”))result.push(“beefy”);if(spec.seasons和spec.seasons.includes(“夏季”))if([“斯巴达”,“亚特兰蒂斯”]。包括(指定国家)结果。推动(“白色闪电”);}    //… rest of conditions

就像任何重构事件一188足球比分直播样,我想采取我能采取的最小措施,因此,我将替换当时最小的命令式代码块。它很容易保持自适应模型和强制代码并行运行。每次更换后,我都会运行此建议逻辑的所有测试,这也是一个回顾这些测试的好机会,看看它们做得有多好。即使我把逻辑转移到数据中,我还需要检查。json文件是数据,但应该作为代码来对待:版本控制和测试的方式是一样的。

季节条件

接下来是逻辑的第二行:

推荐人。ES6…

if(spec.seasons和spec.seasons.includes(“winter”))result.push(“beefy”);

The first thing to notice here is that we have a compound condition,但是这个复合条件在整个代码中重复了很多地方。

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec));如果(规格季节和规格季节包括(冬季)result.push(“beefy”);如果(规格季节和规格季节包括(“夏季”))如果([“斯巴达”,“亚特兰蒂斯”]。包括(指定国家)结果。推动(“白色闪电”);}if(规格提醒>=150)if(规格季节和规格季节包括(“夏季”))if(spec.minduration<350)result.push(“白光”);否则,如果(spec.minduration<570)result.push(“little master”);其他结果。推(“墙”);}else if(spec.minduration<450)result.push(“白色闪电”);else result.push("little master");}    }    return _.uniq(result);}

虽然这是一个复合条件,它只代表一个单一的意图-复合的性质是因为在我测试它的内容之前,我必须检查季节属性是否存在。每当我看到这样的东西,我抽搐地伸手去提取方法.

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec));如果(季节包括(规格,"winter")) result.push("beefy");如果(季节包括(规格,“夏季”))如果([“斯巴达”,“亚特兰蒂斯”]。包括(指定国家)结果。推动(“白色闪电”);}if(规格提醒>=150)if(季节包括(规格,“夏季”))if(spec.minduration<350)result.push(“白色闪电”);否则,如果(spec.minduration<570)result.push(“little master”);其他结果。推(“墙”);}else if(spec.minduration<450)result.push(“白色闪电”);else result.push("little master");}    }    return _.uniq(result);}功能季节包括(规格,arg)返回spec.seasons和spec.seasons.includes(arg);}

完成重构之后,188足球比分直播第二行现在变成带参数的单个函数。在JSON中表示函数名和参数是一种很好的策略,因为它给了我很大的灵活性,所以我来试试这个。

推荐模型.json…

[“条件”:“夜间”,“result”:“耳语死亡”,“条件”:“季节包括”,“conditionargs”:[“冬季”],“result”:“Beefy”]

推荐人。ES6…

函数处于活动状态(规则,spec) {    if (rule.condition === 'atNight') return spec.atNight;if(rule.condition=='季节包括')返回季节包括(spec,rule.conditionargs[0]);抛出新错误(“无法处理”+rule.condition);}

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec));如果(季节包括(规格,"winter")) result.push("beefy");如果(季节包括(规格,“夏季”))如果([“斯巴达”,“亚特兰蒂斯”]。包括(指定国家)结果。推动(“白色闪电”);}if(spec.minduration>=150)if(季节包括(spec,“夏季”))if(spec.minduration<350)result.push(“白色闪电”);否则,如果(spec.minduration<570)result.push(“little master”);其他结果。推(“墙”);}else if(spec.minduration<450)result.push(“白色闪电”);else result.push("little master");}    }    return _.uniq(result);}功能季节包括(规格,arg)返回spec.seasons和spec.seasons.includes(arg);}//函数的其余部分…

Strictly I could just use a single value for the精氨酸,但是函数在某一点上通常需要多个参数,从数组开始并不费力。

提取国家逻辑

第三个条件是这样的

推荐人。ES6…

如果(季节包括(规格,“夏季”))如果([“斯巴达”,“亚特兰蒂斯”]。包括(指定国家)结果。推动(“白色闪电”);}

这介绍了一些事情。首先,有一个新的特性规范要探讨:药水将在哪个国家使用。其次,将国家测试与现有的季节性测试相结合。

我一直在执行这个重构,从顶部开始,一次一个条件。188足球比分直播但我现在承认,我创造了条件,使我们在条件中的复杂性逐渐增加。这有利于教学,but it won't be the way typical code appears in the field.我提倡一次重构一个条件,188足球比分直播像我在这里所做的那样逐步建立自适应模型的表达能力。However the best thing is to look through the code and pick fragments of logic to work with,从简单的事情开始,逐渐变得更复杂。这通常意味着从上到下不是最简单的方法。

通过重构,188足球比分直播我喜欢一次做一件事,所以我将从为国家进行测试开始。就像之前的季节测试一样,我首先将国家测试逻辑提取到它自己的函数中。

推荐人。ES6…

如果(季节包括(规格,“夏季”))如果(包括的国家(规格,[斯巴达],“亚特兰蒂斯”])result.push(“白色闪电”);}

功能国家包括在(规格,anarray)返回anarray.includes(spec.country);}

Parameterizing the model

利用以前的重构,188足球比分直播我的下一步是扩展JSON规则,以合并我将要移动的条件。但对于这个案子,我想先试着处理一下包括的国家自己测试,然后再结合季节性测试。到目前为止,我的测试与之类似。

it('night only',function()assert.include(推荐人(夜间:真)),“耳语死亡”);(});

我在用摩卡为了我的考试

Where I've passed in a spec and run it against the existing recommender logic.但为了检验国家的逻辑,I need to create and pass in a model that contains the country logic without any additional conditions.我不是在测试我的推荐模型,但是一些一般推荐模型的语义。按照规范的规定,我需要用某种双倍试验对于允许我放入简化测试模型的模型。

推荐人。ES6…

函数executeModel(spec)返回getModel().filter((r)=>禁用(r,spec))      .map((r) => r.result)  }

设置这样的双重测试是可行的,但是,所以我更喜欢用不同的方法。首先我会用添加参数这样模型就被传递到引擎中。

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec,getModel());如果(季节包括(规格,“夏季”))如果(国家包括在(规格,[斯巴达],“亚特兰蒂斯”])result.push(“白色闪电”);}//… remaining logic
}

函数executeModel(spec,模型{返回模型.filter((r)=>禁用(r,spec))      .map((r) => r.result)  }

然后,我可以根据以下内容编写一个测试:

it('night only',function()assert.include(executeModel(atNight:true,[“条件”:“夜间”,“result”:“expected”]),“预期”;(});

有了它,我现在可以编写一个测试来纯粹测试国家财产。

它(“国家”)函数()const model=[条件:“countryincludedin”,条件参数:【斯巴达】“亚特兰蒂斯”结果:“应为”];预期(ExecuteModel(国家:“斯巴达”,模型);包括(“预期”);预期(ExecuteModel(国家:“雅典”,模型))。不包括(“预期”);(});

让它通过

推荐人。ES6…

函数处于活动状态(规则,spec) {    if (rule.condition === 'atNight') return spec.atNight;if(rule.condition=='季节包括')返回季节包括(spec,rule.conditionargs[0]);if(rule.condition=='countryincludedin')返回rule.conditionargs.includes(spec.country);抛出新错误(“无法处理”+rule.condition);}

添加连词

Testing for the operating country in the spec isn't all I need to do to handle the third rule:

推荐人。ES6…

如果(季节包括(规格,“夏季”))如果(国家包括在(规格,[斯巴达],“亚特兰蒂斯”])result.push(“白色闪电”);}

我还需要处理条件的嵌套。当使用这样的自适应模型时,我喜欢把逻辑限制在简单的表达式上,嵌套语句导致更复杂的表示。有了嵌套的ifs,this is easy as I can refactor the nested ifs to a conjunction.

推荐人。ES6…

如果(季节包括(规格,“夏令时”)和国家包括在内(规格,[斯巴达],“亚特兰蒂斯”])result.push(“白色闪电”);

所以现在我只需要在引擎中有一个连词(“and”)函数,我可以扩展规则库来覆盖这个案例。

推荐模型.json…

[“条件”:“夜间”,“result”:“耳语死亡”,“条件”:“季节包括”,“conditionargs”:[“冬季”],“result”:“Beefy”,“条件”:“和”,“conditionargs”:[“condition”:“季节包括”,“conditionargs”:[“夏季”],“条件”:“包含国家”,"conditionArgs": ["sparta",“亚特兰蒂斯”]],“result”:“白亮”]

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec,GET模型());如果(季节包括(规格,“夏令时”)和国家包括在内(规格,[斯巴达],“亚特兰蒂斯”)
      结果:推动(“白光”);
//… remaining logic
}

函数处于活动状态(规则,spec) {    if (rule.condition === 'atNight') return spec.atNight;if(rule.condition=='季节包括')返回季节包括(spec,rule.conditionargs[0]);if(rule.condition=='countryincludedin')返回rule.conditionargs.includes(spec.country);if(rule.condition=='and')返回rule.conditionargs.every((arg)=>isactive(arg,);抛出新错误(“无法处理”+rule.condition);}

我希望这三个条件能让您对如何将命令式代码重构为自适应模型有一个很好的了解。我一次转换一个块的逻辑。如果模型不能处理块,我使用扩展模型的组合(添加函数,添加函数参数的功能)并重构命令代码(用conjunction替换嵌套的条件)。188足球比分直播


复杂的钻头

以下是主要推荐功能的当前状态

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec,GET模型());if(spec.minduration>=150)if(季节包括(spec,“夏季”))if(spec.minduration<350)result.push(“白色闪电”);否则,如果(spec.minduration<570)result.push(“little master”);其他结果。推(“墙”);}else if(spec.minduration<450)result.push(“白色闪电”);else result.push("little master");}    }    return _.uniq(result);}

I've folded in the initial lines into the model,所以我现在只剩下大条件了。这似乎不适合生产规则样式。这并不意味着底层逻辑不适合模型,只是代码需要一些按摩,形状才变得清晰。

但这里也有另一个问题。This code is probing a new property of the spec - recommending a variety depending on the minimum duration that you want the potion to last (rather important for a flying potion).条件代码在一定程度上掩盖了更广泛的模式。

范围选取器模式

我经常看到条件代码测试这样的数值

函数somelogic(arg)if(arg<5)返回“low”;else if (arg < 15) return "medium";否则返回“高”;}

代码的核心意图是根据值的范围列表返回值。我可以这样表示相同的逻辑:

函数logicWithPicker(arg)const range=[[5,“低”,[15,“媒介”无穷大,“高”];返回pickfromrange(范围,ARG);}功能选择范围(范围,value)const matchindex=range.findindex((r)=>value<r[0]);返回范围[MatchIndex][1];}

您会注意到,这和我在本文中描述的技巧一样——将逻辑转换为数据。我提出了一个简单的语义模型——断点和返回值表,以及一些执行该模型的行为。

和许多数据变更逻辑一样,我不会一直这样做的。简单的条件逻辑很容易理解,特别是如果格式化整齐以强调其表格方面。但是,如果断点打开频繁更改,然后将它们表示为数据,通常更容易更新。在本例中,通过范围选择器表示此逻辑更适合我将逻辑表示为数据的总体需求。

用范围选择器替换条件

So my first moves in 188足球比分直播refactoring this next batch of code will be to replace the minimum duration tests in the imperative code with a range picker.我先从夏令营开始。

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec,GET模型());康斯特·萨默皮克斯=[[150,NULL,〔350〕'white lightening'],〔570〕“小主人”],无穷大,“墙”];if(spec.minduration>=150)if(季节包括(spec,“夏天”({)结果.push(选择范围(SummerPicks,特殊注意事项);}else if(spec.minduration<450)result.push(“白色闪电”);else result.push("little master");}    }    return _.uniq(result);}

这段代码的一个尴尬之处是,外部条件排除了最短持续时间范围的第一个频带。我想把它去掉,将其逻辑保持在范围选择器内,这意味着我不需要推荐就需要一个值。空似乎是这个的自然选择,尽管在这种情况下使用nulls时我总是有点畏缩。

下一步我再做另一件事

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec,GET模型());康斯特·萨默皮克斯=[[150,NULL,〔350〕'white lightening'],〔570〕“小主人”],无穷大,“墙”];常量非摘要标记=[〔150〕NULL,〔450〕'white lightening'],无穷大,“小主人”];if(spec.minduration>=150)if(季节包括(spec,“夏季”))result.push(选择范围(夏季选择,特殊注意事项);}否则{结果.push(选择范围(非SummerPicks,特殊注意事项);}    }    return _.uniq(result);}

删除外部条件

完成这些任务后,我现在想摆脱外部条件。

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec,GET模型());康斯特·萨默皮克斯=[[150,NULL,〔350〕'white lightening'],〔570〕“小主人”],无穷大,“墙”];const nonSummerPicks = [      [150,NULL,〔450〕'white lightening'],无穷大,“小主人”];如果(规格记忆>=150){如果(季节包括(规格,“夏季”))result.push(选择范围(夏季选择,特殊注意事项);}else result.push(选择范围(非摘要选项,特殊注意事项);}}return _.uniq(result);}

但如果我这样做,the tests fail.这里有几个问题,首先,有条件的不仅仅是检查思维是否不少于150,它还检查它是否存在——这是许多JavaScript操作令人讨厌的宽容性质。这意味着我需要在调用范围选择器函数之前检查这个值。

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec,GET模型());康斯特·萨默皮克斯=[[150,NULL,〔350〕'white lightening'],〔570〕“小主人”],无穷大,“墙”];const nonSummerPicks = [      [150,NULL,〔450〕'white lightening'],无穷大,“小主人”];    如果(规格记忆>=150){如果(季节包括(规格,“夏天”({)如果(规格说明)结果.push(选择范围(SummerPicks,特殊注意事项);}否则{如果(规格说明)结果.push(选择范围(非SummerPicks,特殊注意事项);}}return _.uniq(result);}

That's duplication,所以我申请提取方法.

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec,GET模型());康斯特·萨默皮克斯=[[150,NULL,〔350〕'white lightening'],〔570〕“小主人”],无穷大,“墙”];const nonSummerPicks = [      [150,NULL,〔450〕'white lightening'],无穷大,“小主人”];如果(季节包括(规格,“夏季”))结果.push(pickMinDuration(规格,SummerPicks))其他结果。推送(pickMinDuration(规格,nonSummerPicks));}返回uniq(result);}功能pickMinDuration(规格,range)if(spec.minduration)返回pickfromrange(range,规范注意事项);}

在没有建议的情况下处理范围

但是我仍然有一些测试失败,因为我返回了一个位于结果集中的空值。One way to fix this is to guard the result.

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec,GET模型());康斯特·萨默皮克斯=[[150,NULL,〔350〕'white lightening'],〔570〕“小主人”],无穷大,“墙”];const nonSummerPicks = [      [150,NULL,〔450〕'white lightening'],无穷大,“小主人”];如果(季节包括(规格,“夏天”({)如果(pickminduration(spec,summerPicks))结果。推送(pickminduration(spec,夏季精选)其他{如果(pickminduration(spec,非SummerPicks)结果。推送(pickminduration(spec,nonSummerPicks));}返回uniq(result);}

我可以说这是生产规则的一部分条件,但我认为这样做并不真正符合领域的语义。

另一种选择是过滤掉末尾的空值

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec,GET模型());康斯特·萨默皮克斯=[[150,NULL,〔350〕'white lightening'],〔570〕“小主人”],无穷大,“墙”];const nonSummerPicks = [      [150,NULL,〔450〕'white lightening'],无穷大,“小主人”];如果(季节包括(规格,“夏季”))result.push(pickminduration(spec,summerPicks))    }    else {      result.push(pickMinDuration(spec,nonSummerPicks));}    return _.uniq(result).filter((v)=>空!= V);}

我用“!=“捕捉由返回的空值和未定义值pickMinDuration当没有思维定势财产

虽然这两种方法都有效,我不想像这样把空的东西扔来扔去。如果没有东西可以归还,我宁愿什么也不回,也不愿意什么也不回。有一个经典的方法来处理这个问题-而不是返回一个值,返回一个列表。什么也不返回,只意味着返回空列表。

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec,GET模型());康斯特·萨默皮克斯=[[150,[],〔350〕'white lightening'],〔570〕“小主人”],无穷大,“墙”];const nonSummerPicks = [      [150,[],〔450〕'white lightening'],无穷大,“小主人”];如果(季节包括(规格,“夏天”({)result = result.concat(选择时间(规格,夏季精选)其他{result = result.concat(选择时间(规格,nonSummerPicks));}返回uniq(result);}功能选择(规格,range)如果(spec.minduration)返回pickfromrange(range,规范注意事项);否则返回[]}

javascript定义concat,以便将非数组值添加到数组中。

这让我对我的生产规则代码有点着迷,它必须处理获取数组和值。幸运的是,这是一个共同解决方案的共同问题-展平函数.

推荐人。ES6…

函数executeModel(spec,型号)退货链(模型).filter((r)=>禁用(r,spec)).map((r)=>r.result)平坦()
.value()}

因为普通ES6没有变平,我需要用下划线

移除其他

我的生产规则没有任何概念其他的所以我会用倒if来代替它。

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec,GET模型());康斯特·萨默皮克斯=[[150,[]〔350〕'white lightening'],〔570〕“小主人”],无穷大,“墙”];const nonSummerPicks = [      [150,[]〔450〕'white lightening'],无穷大,“小主人”];如果(季节包括(规格,“夏季”))result=result.concat(pickminduration(spec,夏季精选)否则{
如果(!)季节包括(规格,“夏天”({)结果=result.concat(pickminduration(spec,nonSummerPicks));}返回uniq(result);}

添加结果函数

我现在已经将命令式代码重构为一种形状,使其易于转换为生产规则。But it's still not going to be trivial to replace the imperative code with the production rule because my production rules so far expect to return simple values.这个需要执行pickMinDuration功能。This makes it closer to the classic production rule structure,其中条件和动作都是函数。处理这一问题的一个简单方法是向引擎添加一些处理,以处理结果函数或单个结果值。我将用一系列的小步骤来完成这项工作,首次使用提取方法

推荐人。ES6…

函数executeModel(spec,model) {    return _.chain(model)      .filter((r) => isActive(r,spec)).map((r)=>结果(r)).flatten().value()功能结果(r)返回R.result;}

pickMinDuration采取规范,所以我必须用添加参数

推荐人。ES6…

函数executeModel(spec,model) {    return _.chain(model)      .filter((r) => isActive(r,spec)).map((r)=>结果(r,规格)).flatten().value()函数结果(r,规格)返回R.result;}

现在我将添加最小持续时间规则的处理。因为这有点诡计,我将为它编写一个特定的测试。

测试,ES6…

describe('最小持续时间规则',函数()const range=[[5,[]〔10〕“低”,[  Infinity,“高”];const model=[条件:“pickminduration”,条件参数:【范围】,resultfunction:'选择时间',结果参数:[范围]];常量测试值=[[4.9,[][  5,[低] ]〔9.9〕[低] ]〔10〕[高]];testValues.foreach(函数(v)it(`pick for duration:$v[0]`,函数()预期(ExecuteModel(Minduration:v[0],模型))。深。等于(v[1]);});它(“空规格”,()=>期望(ExecuteModel(,模型)。

然后,我将修改规则引擎中的result函数,以有条件地处理结果值或结果函数以及条件测试,以识别最短持续时间的情况。

推荐人。ES6…

函数executeModel(spec,model) {    return _.chain(model)      .filter((r) => isActive(r,spec)).map((r)=>结果(r,spec))      .flatten()      .value()  }  function result(r,{规格}if (r.result) return r.result;否则,如果(r.resultFunction=='pickminduration')返回pickminduration(spec,r.resultangs[0])}  function isActive(rule,spec)if(rule.condition==‘atnight’)返回spec.atnight;if(rule.condition=='季节包括')返回季节包括(spec,rule.conditionargs[0]);if(rule.condition=='countryincludedin')返回rule.conditionargs.includes(spec.country);if(rule.condition=='and')返回rule.conditionargs.every((arg)=>isactive(arg,);if(rule.condition=='pickminduration')返回true;抛出新错误(“无法处理”+rule.condition);}

现在一切就绪,我可以很容易地向模型中添加规则并删除第一个条件。

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec,GET模型());夏季精选=[
    〔150〕[]
    〔350〕'white lightening'],
    〔570〕“小主人”],
    无穷大,'wall']
    const nonSummerPicks = [      [150,[]〔450〕'white lightening'],无穷大,“小主人”];如果(季节包括(规格,“夏天”({)
    结果=result.concat(pickminduration(spec,summerPicks))
    }如果(!)季节包括(规格,“夏季”))result=result.concat(pickminduration(spec,nonSummerPicks));}返回uniq(result);}

推荐模型.json…

[“条件”:“夜间”,“result”:“耳语死亡”,“条件”:“季节包括”,“conditionargs”:[“冬季”],“result”:“Beefy”,“条件”:“和”,“conditionargs”:[“condition”:“季节包括”,“conditionargs”:[“夏季”],“条件”:“包含国家”,"conditionArgs": ["sparta",“亚特兰蒂斯”]],"result": "white lightening"    },“条件”:“季节包括”,“conditionargs”:[“夏季”],“resultfunction”:“pickminduration”,“resultargs”:[[[150,【】,〔350〕“白色闪电”],〔570〕“小主人”],[无穷大],“墙”]]]]

删除简单结果值

这个很好用,但我不喜欢我如何拥有结果值或结果函数以及对它的条件处理。我只需要结果函数就可以使事情更规则化,and have a value function that just returns its arguments.

推荐模型.json…

[“条件”:“夜间”,“result”:“值”,“resultangs”:[“耳语死亡”]}“条件”:“季节包括”,“conditionargs”:[“冬季”],“result”:“值”,“resultArgs”:[“beefy”]}“条件”:“和”,“conditionargs”:[“condition”:“季节包括”,“conditionargs”:[“夏季”],“条件”:“包含国家”,"conditionArgs": ["sparta",“亚特兰蒂斯”]],“result”:“值”,
“resultArgs”:[“白色变亮”]}{“条件”:“seasonIncludes”,“conditionargs”:[“夏季”],“result”:“pickminduration”,“resultargs”:[[[150,【】,〔350〕“白色闪电”],〔570〕“小主人”],[无穷大],“墙”]]]]

推荐人。ES6…

函数结果(r,{规格}if(r.result==“value”)返回r.resultArgs[0];如果(r.result=='pickminduration')返回pickminduration(spec,r.resultangs[0]);引发新错误(“未知结果函数:”+r.result)}

这使得模型JSON更加冗长,但允许引擎更加规则。在这种情况下,我更喜欢更规则的模型,即使它更冗长。I can fix the verbosity another way,我要说的是后来.

添加否定条件

为了将条件的最后一段引入模型,我需要在模型中有一个否定函数。

推荐人。ES6…

函数处于活动状态(规则,spec) {    if (rule.condition === 'atNight') return spec.atNight;if(rule.condition=='季节包括')返回季节包括(spec,rule.conditionargs[0]);if(rule.condition=='countryincludedin')返回rule.conditionargs.includes(spec.country);if(rule.condition=='and')返回rule.conditionargs.every((arg)=>isactive(arg,);if(rule.condition=='pickminduration')返回true;如果(rule.condition=='not')返回!isActive(rule.conditionargs[0],规格;抛出新错误(“无法处理”+rule.condition);}

然后我可以去掉最后一点命令逻辑。

推荐人。ES6…

export default function (spec) {    let result = [];结果=result.concat(executeModel(spec,GET模型());常量非摘要标记=[
    〔150〕[]
    〔450〕'white lightening'],
    无穷大,“小主人”]
    
    如果(!)季节包括(规格,“夏天”({)
    结果=result.concat(pickminduration(spec,nonSummerPicks));
    }return _.uniq(result);}

添加到recommendationmodel.json…

“条件”:“不”,"conditionArgs": [{"condition":"seasonIncludes",“conditionargs”:[“夏季”]],“result”:“pickminduration”,“resultArgs”:[[150,【】,〔450〕“白色闪电”],[无穷大],“小主人”]]]

模型而不是代码

所有这些都完成了,所有条件逻辑都已从这个原始命令式代码移走

推荐人。ES6…

export default function (spec) {    let result = [];如果(规格:夜间)result.push(“耳语死亡”);if(spec.seasons和spec.seasons.includes(“winter”))result.push(“beefy”);if(spec.seasons和spec.seasons.includes(“夏季”))if([“斯巴达”,“亚特兰蒂斯”]。包括(指定国家)结果。推动(“白色闪电”);}if(spec.minduration>=150)if(spec.seasons&&spec.seasons.includes(“summer”))if(spec.minduration<350)result.push(“white lighting”);否则,如果(spec.minduration<570)result.push(“little master”);其他结果。推(“墙”);}else if(spec.minduration<450)result.push(“白色闪电”);else result.push("little master");}    }    return _.uniq(result);}

进入这个JSON模型

推荐模型.json…

[“条件”:“夜间”,“result”:“值”,“resultangs”:[“耳语死亡”],“条件”:“季节包括”,“conditionargs”:[“冬季”],“result”:“值”,“resultArgs”:[“Beefy”],“条件”:“和”,“conditionargs”:[“condition”:“季节包括”,“conditionargs”:[“夏季”],“条件”:“包含国家”,"conditionArgs": ["sparta",“亚特兰蒂斯”]],“result”:“值”,“resultArgs”:[“白色变亮”],{“条件”:“seasonIncludes”,“conditionargs”:[“夏季”],“result”:“pickminduration”,“resultargs”:[[[150,【】,〔350〕“白色闪电”],〔570〕“小主人”],[无穷大],“墙”]]],“条件”:“不”,"conditionArgs": [{"condition":"seasonIncludes",“conditionargs”:[“夏季”]],“result”:“pickminduration”,“resultArgs”:[[150,【】,〔450〕“白色闪电”],[无穷大],“小主人”]]]]

使用以下引擎来解释JSON模型

推荐人。ES6…

export default function (spec) {    return executeModel(spec,GETMODEM());}功能选择(规格,范围)返回(规格提醒)?pickFromRange(range,规格说明:【】;}功能国家包括在(规格,anarray)返回anarray.includes(spec.country);}功能季节包括(规格,arg)返回spec.seasons和spec.seasons.includes(arg);}函数executeModel(spec,model) {    return _.chain(model)      .filter((r) => isActive(r,spec)).map((r)=>结果(r,spec))      .flatten()      .uniq()      .value()  }  function result(r,spec) {    if (r.result === "value") return r.resultArgs[0];如果(r.result=='pickminduration')返回pickminduration(spec,r.resultangs[0]);引发新错误(“未知结果函数:”+r.result)函数处于活动状态(规则,spec) {    if (rule.condition === 'atNight') return spec.atNight;if(rule.condition=='季节包括')返回季节包括(spec,rule.conditionargs[0]);if(rule.condition=='countryincludedin')返回rule.conditionargs.includes(spec.country);if(rule.condition=='and')返回rule.conditionargs.every((arg)=>isactive(arg,);if(rule.condition=='pickminduration')返回true;如果(rule.condition=='not')返回!isActive(rule.conditionargs[0],规格;抛出新错误(“无法处理”+rule.condition);}

我对前面的代码做了一些小的清理。

What have I gained and lost?首先,代码现在大了一点,JSON模型和引擎分别比原始代码大。独自一人,那是件坏事。重要的收获,然而,is that we now have a single representation of the recommendation logic that can be interpreted on a website,网间网操作系统,安卓,or any other environment that can read a JSON file.这是一个相当大的优势,特别是如果逻辑实际上比我在这里看到的要大-你应该看到隐形药水的推荐逻辑。

这里还有一个问题:自适应模型是否比命令式代码更容易修改。虽然它更大,它比较规则。有了一套更大的规则,imperative code's flexibility can let it get more tangled easily,虽然自适应模型的表达能力有限,但有助于保持逻辑更容易理解。Many people favor adaptive models for this reason,即使他们没有我们在亚特兰蒂斯面临的多重执行环境问题。

我还应该总结重构过程。188足球比分直播一旦我意识到我需要用一个自适应模型替换一些命令式代码,I first sketch out a first draft of that adaptive model - hopefully using one that's well-known.然后我取一些命令式代码的小部分,用填充自适应模型来替换它们。如果代码与模型不匹配,我将把它重构成一个合适的形状,然后把它移过来。如果自适应模型不能很好地处理当前代码片段,我将重构模型。

在这个例子中,我用模型替换了所有必需的代码,but I don't have to do that.在任何时候,我都可以停止并在模型中留下一些逻辑,在命令式代码中留下一些逻辑。这对于边缘情况非常有用,因为边缘情况会增加模型的复杂性,不值得扩展模型来处理。在这种情况下,我们会接受复制和应用程序存储对这些边缘情况的不便,同时能够通过模型更新处理大多数规则更改。


一些进一步的重构188足球比分直播

当我写这篇文章的时候,重构的两个更进一步的方向正在对我大喊大叫。188足球比分直播我可以重新阅读这篇文章,在另一天添加这些内容。

重新组织模型

当我查看JSON模型时,我想应该稍微重新组织一下它的结构,所以那不是

{  "condition": …  "conditionArgs": …  "result": …  "resultArgs": …}

我会

“condition”:“name”:…“args”:…“result”:“name”:…“args”:…

这使得结构更加规则。在逐步迁移这个数据结构时,这里有一些有趣的重构。188足球比分直播

用查找替换命令式分派

发动机当前使用活动的结果功能。基本上连接一个case语句(当然,如果我是一个很酷的函数式程序员,我称之为模式匹配)。另一种选择是用查找系统替换命令式代码,哪里的条件季节包括通过查找表或反射自动与函数匹配。

Representing the model with a DSL

The JSON model reads fairly well,但是JSON语法限制了我如何清晰地表示规则。此外,我故意喜欢模型中的规则性,即使它使模型比它可能的更冗长。如果我管理很多规则,我想介绍一个特定于域的语言为此,内部(使用javascript)或外部。这可以让人更容易理解,因此修改,推荐规则。

消除原始的困扰

代码代表了板球品种的概念,季节,国家都是一条线。虽然这模拟了它们在JSON中的表示方式,为这样的概念创建特定的类型通常是明智的。这澄清了代码本身,并提供一个可以吸引有用行为的家。

验证自适应模型

目前,我只能通过执行自适应模型来检测错误。As models get more complex it's useful to build a validation operation that can detect that the JSON is well-formed and follows implicit syntactic rules beyond the simple structure enforced by JSON.这些规则将表明每个子句都必须有一个条件和一个结果,以及季节包括函数必须是已知的季节。

逆向重构188足球比分直播

就像任何重构一样,188足球比分直播there is also the reverse motion: replacing an adaptive model with imperative code.这也是一个值得走的方向-自适应模型可能难以维护,尤其是因为这是一种不太熟悉的方法。我经常遇到这样一种情况,一个团队中的一些经验丰富的成员通过操纵自适应模型来实现真正的生产力,但是团队中的其他人都觉得很难合作。在某些情况下,额外的生产力使它值得生活,but sometimes there are no benefits to the adaptive model.当人们第一次遇到代码时,通常会对将代码表示为数据的可能性感到兴奋,因此使用过度。That's not a problem,这是自然学习过程的一部分,但一旦团队意识到他们走得太远,就必须将其移除。

用命令式代码替换自适应模型与它的逆过程类似,在这一点上,您首先设置了一些东西,这样您就可以用命令式代码组合模型的结果,然后将逻辑分块移动到命令代码中,随时随地测试。最大的不同是你可以,几乎总是应该,让命令式代码的结构与自适应模型的结构相同。因此,在从命令式代码转移到模型时,不会对模型或代码结构进行任何按摩。


分享:
如果你觉得这篇文章有用,请分享。我感谢你的反馈和鼓励

关于类似主题的文章…

…查看以下标签:

188足球比分直播 特定于域的语言


Acknowledgements

Andrew SlocumChelsea Komlo克里斯蒂安·特雷波,雨果·科布奇在我们的内部邮件列表上评论了这篇文章的草稿。Jean no_l Rouvignac指出了一些打字错误。

重要修改

2015年11月19日:出版了第二期和最后一期

2015年11月11日:出版了第一部分