假设我们正在编写一个电子表格验证函数。用户可以在电子表格中输入多个值,并且有一种方法可以验证这些值是否正确。除了验证它们是否正确之外,还会弹出一个“帮我修复”对话框,询问用户是否要自动修复问题。
出于示例目的,假设我们有以下字段:
- 事件网址:事件链接。
- 事件标题:日历事件的名称。
- 受邀者:应受邀参加事件的用户的逗号分隔电子邮件地址列表。
然后用户可以点击“验证”按钮来检查以下内容:
- 事件标题与网址中的标题确实匹配。如果没有,他们会看到更新标题的选项。
- 受邀者都参加了事件。如果没有,则会向用户显示邀请下一位的选项(一次只能执行一次)。
反复执行一组函数的良好编程设计模式是什么?
function validateSpreadsheet() {
validateEventTitle();
validateInvitees();
}
validateEventTitle
和 validateInvitees
都应返回 3 个可能值之一:
- 成功
- 重试(用户选择使用“帮我修复”按钮。)
- 错误(用户没有选择“修复它”功能。)
如果其中一个返回 Retry,则应运行整个方法 validateSpreadsheet
(例如,如果我们决定让事件标题取决于被邀请者的数量)。
我可以想到函数 validateSpreadsheet
重复其逻辑的几种方法:
- (A) While 循环
- (B) 递归
- (C) 函数数组
我可以想到函数 validateEventTitle
报告其状态的几种方法:
- (1) 它可以返回一个包含 3 个值(成功、重试、错误)的枚举
- (2) 在重试和/或错误的情况下可能会引发异常
我为解决方案 C1 实现了伪代码(请参阅帖子末尾),但 C1 使得在不同方法之间共享代码变得困难。例如,如果代码的主要内容如下所示:
function validateSpreadsheet() {
var row = getRow();
var title = getEventTitle(row);
validateEventTitle(title, row);
validateInvitees(row);
}
...使用 C1 会更困难,因为方法被包装在函数中。我意识到有一些方法可以解决这个限制。
我不喜欢解决方案 B1,但为了完整起见,我也在下面提供了它的一个版本。我不喜欢它使用调用堆栈进行重复。我还认为代码由于双重 if
检查而非常困惑。我意识到我可以创建辅助方法,使其成为每个方法的单个 if
检查,但这仍然相当困惑。
我实现了解决方案 A2 的工作示例。这个似乎运行良好,但它大量利用异常,这可能会让新程序员感到困惑。控制流程不容易遵循。
是否已经有一种设计模式可以实现这样的目标?我想使用它而不是重新发明轮子。
<小时/>解决方案C1(伪代码)
function solutionC1() {
var functions = [
method1,
method2
];
while (true) {
var result = SUCCESS;
for (var f in functions) {
result = f();
if (result === SUCCESS) {
continue;
} else if (result === REPEAT) {
break;
} else {
return result; // ERROR
}
}
if (result === REPEAT) {
continue;
} else {
return; // SUCCESS
}
}
}
解决方案 B1(伪代码)
function solutionB1() {
var result;
result = method1();
if (result === RETRY) {
return solutionB1();
} else if (isError(result)) {
return result;
}
result = method2();
if (result === RETRY) {
return solutionB1();
} else if (isError(result)) {
return result;
}
}
解决方案 A2(使用单元测试)
function solutionA2() {
while (true) {
try {
// these two lines could be extracted into their own method to hide the looping mechanism
method1();
method2();
} catch(error) {
if (error == REPEAT) {
continue;
} else {
return error;
}
}
break;
}
}
var REPEAT = "REPEAT";
var method1Exceptions = [];
var method2Exceptions = [];
var results = [];
function unitTests() {
// no errors
method1Exceptions = [];
method2Exceptions = [];
results = [];
solutionA2();
if (results.join(" ") !== "m1 m2") { throw "assertionFailure"; }
// method1 error
method1Exceptions = ["a"];
method2Exceptions = ["b"];
results = [];
solutionA2();
if (results.join(" ") !== "m1:a") { throw "assertionFailure"; }
// method1 repeat with error
method1Exceptions = [REPEAT, "a"];
method2Exceptions = ["b"];
results = [];
solutionA2();
if (results.join(" ") !== "m1:REPEAT m1:a") { throw "assertionFailure"; }
// method1 multiple repeat
method1Exceptions = [REPEAT, REPEAT, REPEAT, "a"];
method2Exceptions = ["b"];
results = [];
solutionA2();
if (results.join(" ") !== "m1:REPEAT m1:REPEAT m1:REPEAT m1:a") { throw "assertionFailure"; }
// method1 multiple repeat, method2 repeat with errors
method1Exceptions = [REPEAT, REPEAT, REPEAT];
method2Exceptions = [REPEAT, REPEAT, "b"];
results = [];
solutionA2();
if (results.join(" ") !== "m1:REPEAT m1:REPEAT m1:REPEAT m1 m2:REPEAT m1 m2:REPEAT m1 m2:b") { throw "assertionFailure"; }
// method1 multiple repeat, method2 repeat with no errors
method1Exceptions = [REPEAT, REPEAT, REPEAT];
method2Exceptions = [REPEAT, REPEAT];
results = [];
solutionA2();
if (results.join(" ") !== "m1:REPEAT m1:REPEAT m1:REPEAT m1 m2:REPEAT m1 m2:REPEAT m1 m2") { throw "assertionFailure"; }
// [REPEAT, "Test"];
}
function method1() {
// in reality, this method would do something useful, and return either success, retry, or an exception. To simulate that for unit testing, we use an array.
var exception = method1Exceptions.shift();
if (typeof exception !== "undefined") {
results.push("m1:" + exception);
throw exception;
} else {
results.push("m1");
}
}
function method2() {
// in reality, this method would do something useful, and return either success, retry, or an exception. To simulate that for unit testing, we use an array.
var exception = method2Exceptions.shift();
if (typeof exception !== "undefined") {
results.push("m2:" + exception);
throw exception;
} else {
results.push("m2");
}
}
unitTests();
最佳答案
为了简洁、干净的代码,我建议让导致错误的函数实际上抛出错误,如果它们还没有这样做的话。这允许抛出的任何错误立即渗透到顶部包含的 try
block :
const fns = [
method1,
method2
];
// If the methods return errors but don't throw them, pipe them through isError first:
const fnsThatThrow = fns.map(fn => () => {
const result = fn();
if (isError(result)) {
throw new Error(result);
}
return result;
});
然后,您所要做的就是检查任一函数调用是否会导致 REPEAT
(在这种情况下,递归调用 validateSpreadsheet
),这可以通过 来实现>Array.prototype.some
:
function validateSpreadsheet() {
if (fnsThatThrow.some(fn => fn() === REPEAT)) {
return validateSpreadsheet();
}
}
try {
validateSpreadsheet();
} catch(e) {
// handle errors
}
关于javascript - 多次重复一组方法的设计模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53992646/