在数学中,顺序很重要。
取两个函数f和g并将它们组合,从而将它们作为f(g(x))或g(f(x))应用于参数。 通常,不能假定f(g(x))= g(f(x)) 。 例如,如果f(x)= 2x和g(x)= x 2 ,则f(g(3))= 18但g(f(3))= 36 。 功能应用的顺序很重要。 数学通常提供多种表达想法的方法,该f(g(x))也可以使用成分运算符o来表示,即“应用于”,使得f(g(x))= fog(x) 。
仅根据其他功能的组成定义的功能本身就是一个组合功能。 在Java中,组合方法可能如下所示:
public void recoverFromException() {
View view = registry.get(View.class)
view.endWaiting();
}
如果我们希望从一种方法对其他方法的调用顺序的角度来看一个方法,那么,除去变量和赋值的混乱,我们可以使用“顺序方程式”对该方法进行“建模”:
recoverFromException = endWaiting o get
这样的组合源于所涉及的调用的必要关系, get()为endWaiting()提供了参数。 即得到()endWaiting()之前被调用的语法订货要求的明确性,否则程序将无法编译。 但是看似无关的方法调用又是什么呢? 考虑以下:
public void clearViewCache(View view) {
view.clearPositionCache();
view.clearImageCache();
}
此方法表示的语法排序要求比前一种方法弱,因为它不是组合方法,而是根据两个方法调用定义的,而这两个方法都不向另一个提供必要的参数。 一种其调用顺序缺乏必要的明确性的方法称为或有方法。 与先前的方法不允许重新排序不同,此偶然方法显然可以写成:
public void clearViewCache(View view) {
view.clearImageCache();
view.clearPositionCache();
}
当然,事实并非如此。 由于不带参数且不提供返回值,这两个方法调用都具有副作用,因此第二个调用可能会收集第一个调用更改的信息,从而重新排序会导致错误。 显然缺乏必要的命令是否意味着要遵守秘密命令或随意洗牌是没有根据的; 重点仍然是,该方法本身无论哪种方式都没有提供语法证据,这样做表明组合没有提供语法上的无序性。 这个建议可能是错误的,但仍然存在。 偶然性提出了一个问题,即构图可以完全避免。
程序员重视意图。 例如,肯特·贝克(Kent Beck)的《极限编程说明》(Extreme Programming Explained)声称,简单代码的本质是“揭示其意图”。 罗伯特·C·马丁(Robert C. Martin )进一步断言,“架构不仅仅是意图”,而不仅仅是编程。 约束的增加通常与意图的增加相关,因为随着约束的增加,替代方案的数量会减少,因此产生歧义和误解的机会也会减少。 考虑到组合比顺序约束更多地限制了排序,组合方法比偶然方法显示出更多的意图,因此仅在此狭窄维度上就享有优势。
一些符号可能会有所帮助。 正如o运算符表示合成,我们可以说\运算符表示偶发性。 因此,第二个片段可以写成:
clearViewCache = clearPositionCache \ clearImageCache
根据定义,这等于:
clearViewCache = clearImageCache \ clearPositionCache
程序员如何使用这种琐碎性来阐明意图? 一种方法是最小化或有方法的数量并最大化组合方法的数量。 ,,在这里,由于一些不幸的组合规则,整个宇宙都在嘲笑程序员。 松散地:
组成+组成=组成。
应急+应急=应急。
构成+偶然性=偶然性。
也就是说, fogoh构成了一个组合方法,但是fo(g \ h)是一个或有方法,即使它包含一个组合算符。 一旦被命令的歧义所破坏,任何方法都无法逃脱偶然性。 然而,掩饰在混乱的布下甚至是部分意图似乎是可耻的。 因此,让我们进一步说,一种仅围绕组合运算符或意外事件运算符形成的方法-例如,调用序列k \ m \ p \ s或序列komopos –我们将称其为“简单订单”方法,而同时拥有组合和应急算子的人,我们将其称为“订单复合”。 因此,将意图最大化就变成了将阶数复合方法粉碎成阶数简单的碎片。
考虑:
a = f \(g o h)
如果我们引入一个组合方法j ,使得j = goh,那么上面的order-compound简化为两个order-simple方法:
a = f \ j
j = g o h
如图1所示,这种减少是以增加额外方法和深度为代价的,但是原始方法的偶然性混淆了其总体调用顺序,至少其中一种新方法明确希望其表达顺序。 即使总的意外事件发生,程序的一部分也已从意外事件中保存。
遗憾的是,不存在任何保证将订单复合方法分解为一组简单订单方法的保证。
调用多参数方法固有地涉及偶然性,因为方法不决定功能参数的评估顺序。
(实际上,\运算符只不过是分隔函数参数的逗号。)然后出现两个问题。
给定一个成功的归约法,那么定序复合方法分解为的简单定序方法集合是否唯一?
物产
偶然性的一些属性:
f o (g \ h) = f(g, h)
- 可交换性:
f \ g = g \ f
- 关联性:
(f \ g) \ h = f \ (g \ h)
- 偶发权的分配权:
(f \ g) o h = (f o h) \ (g o h)
其他存在,将在以后的文章中进行研究。
组成简单的订单
让我们检查一些实际的生产代码。 FitNesse提供以下所有代码段,第一个来自PageDriver类。
private NodeList getMatchingTags(NodeFilter filter) throws Exception {
String html = examiner.html();
Parser parser = new Parser(new Lexer(new Page(html)));
NodeList list = parser.parse(null);
NodeList matches = list.extractAllNodesThatMatch(filter, true);
return matches;
}
将其建模为其基本方法调用可得出以下公式:
getMatchingTags = extractAllNodesThatMatch o parse o new Parser o new Lexer o new Page o html
这提供了一个完美的,顺序简单的组合方法,每种方法的调用都取决于先前的方法。 在表达不容篡改的命令时,程序员可以减轻程序员对潜在替代方法的负担,从而帮助阐明方法的意图。 作为简单的订单, PageDriver不提供减少的订单复杂度。
偶然订单简单
public void close() throws IOException {
super.close();
removeStopTestLink();
publishAndAddLog();
maybeMakeErrorNavigatorVisible();
finishWritingOutput();
}
TestHtmlFormatter类中的此方法表示订单简单偶然性,其等式为:
close = close / removeStopTestLink / publishAndAddLog / maybeMakeErrorNavigatorVisible / finishWritingOutput
同样,由于顺序简单, TestHtmlFormatter不可简化,但是与前面的示例不同,这很难说明该方法的设计。 程序员的意图对所使用的语法没有任何痕迹,完全解决了朦胧的语义问题。 仍然,至少该方法是简单的顺序而不是顺序复合:将偶然性集中到这种纯粹的顺序隐含性中,至少会为顺序复合减少所指向的意图揭示重构选择出目标。 重复一遍,降低阶数化合物本身并不会降低整个系统的意外情况,它们会重构正在研究的方法,但不会重构正在研究的方法所调用的方法。 取而代之的是,还原物起着炼油厂的作用,将偶发的污泥泵入金属桶中,供以后排毒。
有序化合物
MultiUserAuthenticator构造函数提供了一个简单的订单复合示例:
public MultiUserAuthenticator(String passwdFile) throws IOException {
PasswordFile passwords = new PasswordFile(passwdFile);
users = passwords.getPasswordMap();
cipher = passwords.getCipher();
}
显然,有序复合条件方法可以通过以下方式建模:
MultiUserAuthenticator = (getPasswordMap \ getCipher) o new PasswordFile
执行有序化合物的还原涉及重构一种方法,以最小化构造该方法的有序化合物的方法。 要执行上述操作,最明显的简化是提取一个新方法f = getPasswordMap \ getCipher ,从而产生两个方程:
MultiUserAuthenticator = f o new PasswordFile
f = getPasswordMap \ getCipher
这样就消除了所有阶数复合,从而产生了组合方法和阶数简单或有方法,从而完成了归约。 产生的源代码如下所示:
public MultiUserAuthenticator(String passwdFile) throws IOException {
PasswordFile passwords = new PasswordFile(passwdFile);
f(passwords);
}
private f(PasswordFile passwords) throws IOException {
users = passwords.getPasswordMap();
cipher = passwords.getCipher();
}
(可能存在方法f()的更好名称。)
同样,这种减少导致增加深度的成本。 但是,其他的减少也会出现。 给定上面的规则(4),构造函数的原始模型可能被编写为:
MultiUserAuthenticator = (getPasswordMap o new PasswordFile) \ (getCipher o new PasswordFile)
这将简化为三个简单的方程式:
MultiUserAuthenticator = f \ g
f = getPasswordMap o new PasswordFile
g = getCipher o new PasswordFile
但是,这样的表述有很多缺点,例如两次调用PasswordFile构造函数。 如果这是一段计算量很大的代码,那么仅凭性能可能会使第二种说法难以为继。 减少仍然回答了我们的问题。 订单复合方法分解成的一组简单订单方法是否唯一? 没有。
归一化问题
PageHistory的processTestFile()方法阐明了一些有趣的困难:
void processTestFile(TestResultRecord record) throws ParseException {
Date date = record.getDate();
addTestResult(record, date);
countResult(record);
setMinMaxDate(date);
setMaxAssertions(record);
pageFiles.put(date, record.getFile());
}
前面的示例介绍了方法调用的重复,以此作为探索不同顺序安排的手段。 但是,如果没有重复,就不能表示processTestFile()的顺序方程式。 在最小的情况下,它的getDate()似乎出现了两次:
processTestFile = put o (getDate \ getFile) \ setMaxAssertions \ (setMinMaxDate o getDate) \ countResult \ (addTestResult o getDate)
= put o (getDate \ getFile) \ setMaxAssertions \ countResult \ ((setMinMaxDate \ addTestResult) o getDate)
如果源代码中的方法调用与其在阶数方程中的出现之间存在一一对应的关系,则可以说所研究的方法是正常形式。 此processTestFile()的格式不是正常的,因此,尽管仍然可以简化,但重复执行调用的性能可能不切实际。
为了完整起见,如果我们引入a = put o(getDate \ getFile)和b =(setMinMaxDate \ addTestResult) ,则得到:
processTestFile = a \ setMaxAssertions \ countResult \ (b o getDate)
最后,我们介绍c = bo getDate我们得到:
processTestFile = a \ setMaxAssertions \ countResult \ c
现在,这是一个简单的或有顺序的方法,将其余的顺序复合隔离到a()方法中,尽管从a()和c()都调用了getDate () 。
摘要
程序员以怀疑的态度看待绝对值。
权变原则是图莱加坦(Tulegatan)原则中最薄弱的一点 ,它在样式指导的波涛汹涌的水面之上几乎无法控制。 考虑到其关注的目标,这也许并不令人惊讶:不是具体的波纹效应而是无定形的意图。 该原则指出应将偶然性降至最低,而不是提倡警惕性消除方法调用的语义顺序,而应提倡使用句法约束的语义顺序。
奇怪的是,尽管偶然性原理很弱,但它导致代码结构明显。 当新手努力在简化顺序和增加代码深度之间取得平衡时,精通顺序化合物缩减工具的战士会产生稀有敏锐度的重构。
马丁为类开发的SOLID原则经常在方法级别找到应用程序,许多程序员也呼吁该方法遵守单一责任原则。 当然,“单一责任”对于不同的人而言意味着不同的事情,但是有人会认为,组合方法比定单简单或有方法更遵守该原则,并且两者都大大超出了定序复合方法。 这样,偶然性提供了一个客观的梯度,可以根据该梯度评估主观原则。
这篇文章的下一部分将研究下降的淘汰,自然产生的订单分层和压实。
翻译自: https://www.javacodegeeks.com/2014/04/structural-contingency-part-one.html