我在https://github.com/arialdomartini/Back-End-Developer-Interview-Questions#snippets
上找到了这个问题我很好奇你的意见,我只是找不到这个重构的合适解决方案,以及在这种非常常见的情况下适用的模式。
function()
{
HRESULT error = S_OK;
if(SUCCEEDED(Operation1()))
{
if(SUCCEEDED(Operation2()))
{
if(SUCCEEDED(Operation3()))
{
if(SUCCEEDED(Operation4()))
{
}
else
{
error = OPERATION4FAILED;
}
}
else
{
error = OPERATION3FAILED;
}
}
else
{
error = OPERATION2FAILED;
}
}
else
{
error = OPERATION1FAILED;
}
return error;
}
你知道如何重构这个吗?
答案 0 :(得分:2)
实际上,我觉得重构的空间比Sergio Tulentsev所建议的要多。
您所链接的回购中的问题更多的是关于代码对话,而不是封闭式问题。因此,我认为有必要讨论该代码的气味和设计缺陷,以建立重构目标。
我看到了这些问题:
if
/ else
分支; Succeeded()
在每个分支中重复; if
/ else
的结构一遍又一遍地复制; error
的分配重复。if
语句主体,这可能会造成混淆。让我们看看该怎么做。
在这里,我使用的是C#实现,但是无论使用哪种语言,都可以执行类似的步骤。
我重命名了某些元素,因为我相信遵守命名约定是重构的一部分。
internal class TestClass
{
HResult SomeFunction()
{
var error = HResult.Ok;
if(Succeeded(Operation1()))
{
if(Succeeded(Operation2()))
{
if(Succeeded(Operation3()))
{
if(Succeeded(Operation4()))
{
}
else
{
error = HResult.Operation4Failed;
}
}
else
{
error = HResult.Operation3Failed;
}
}
else
{
error = HResult.Operation2Failed;
}
}
else
{
error = HResult.Operation1Failed;
}
return error;
}
private string Operation1()
{
// some operations
return "operation1 result";
}
private string Operation2()
{
// some operations
return "operation2 result";
}
private string Operation3()
{
// some operations
return "operation3 result";
}
private string Operation4()
{
// some operations
return "operation4 result";
}
private bool Succeeded(string operationResult) =>
operationResult == "some condition";
}
internal enum HResult
{
Ok,
Operation1Failed,
Operation2Failed,
Operation3Failed,
Operation4Failed,
}
}
为了简单起见,我假设每个操作都返回一个字符串,并且成功或失败是基于对字符串的相等性检查,但是当然可以是任何东西。在接下来的步骤中,如果代码独立于结果验证逻辑,那就太好了。
最好在一些测试工具的支持下开始重构。
public class TestCase
{
[Theory]
[InlineData("operation1 result", HResult.Operation1Failed)]
[InlineData("operation2 result", HResult.Operation2Failed)]
[InlineData("operation3 result", HResult.Operation3Failed)]
[InlineData("operation4 result", HResult.Operation4Failed)]
[InlineData("never", HResult.Ok)]
void acceptance_test(string failWhen, HResult expectedResult)
{
var sut = new SomeClass {FailWhen = failWhen};
var result = sut.SomeFunction();
result.Should().Be(expectedResult);
}
}
我们的案子微不足道,但是作为测验应该是求职面试的问题,我不会忽略它。
第一个重构可能是摆脱可变状态:每个if分支都只能返回值,而不是突变变量error
。另外,名称error
具有误导性,因为它包含成功案例。让我们摆脱它:
HResult SomeFunction()
{
if(Succeeded(Operation1()))
{
if(Succeeded(Operation2()))
{
if(Succeeded(Operation3()))
{
if(Succeeded(Operation4()))
return HResult.Ok;
else
return HResult.Operation4Failed;
}
else
return HResult.Operation3Failed;
}
else
return HResult.Operation2Failed;
}
else
return HResult.Operation1Failed;
}
我们摆脱了空的if
主体,与此同时使代码更易于推理。
如果现在我们反转每个if
语句(Sergio建议的步骤)
internal HResult SomeFunction()
{
if (!Succeeded(Operation1()))
return HResult.Operation1Failed;
if (!Succeeded(Operation2()))
return HResult.Operation2Failed;
if (!Succeeded(Operation3()))
return HResult.Operation3Failed;
if (!Succeeded(Operation4()))
return HResult.Operation4Failed;
return HResult.Ok;
}
我们很明显地看到代码执行了一系列的执行:如果一个操作成功,则调用下一个操作;否则,链条会中断,并出现错误。 GOF Chain of Responsibility Pattern浮现在脑海。
我们可以将每个操作移到一个单独的类,并让我们的函数接收一系列操作以一次执行。每个班级都将处理其特定的操作逻辑(遵守单一责任原则)。
internal HResult SomeFunction()
{
var operations = new List<IOperation>
{
new Operation1(),
new Operation2(),
new Operation3(),
new Operation4()
};
foreach (var operation in operations)
{
if (!_check.Succeeded(operation.DoJob()))
return operation.ErrorCode;
}
return HResult.Ok;
}
我们完全摆脱了if
(只有一个)。
注意如何:
IOperation
接口,这是使函数与操作脱钩的一项初步举措,符合Dependency Inversion Principle; Check
中,并注入到主类中(满足了依赖倒置和单一职责)。internal class SimpleStringCheck : IResultCheck
{
private readonly string _failWhen;
public Check(string failWhen)
{
_failWhen = failWhen;
}
internal bool Succeeded(string operationResult) =>
operationResult != _failWhen;
}
我们获得了在不修改主类的情况下切换检查逻辑的能力(开闭原理)。
每个操作已移至一个单独的类,例如:
internal class Operation1 : IOperation {
public string DoJob()
{
return "operation1 result";
}
public HResult ErrorCode => HResult.Operation1Failed;
}
每个操作都知道其自己的错误代码。该功能本身变得独立于此。
还有更多需要重构的代码
foreach (var operation in operations)
{
if (!_check.Succeeded(operation.DoJob()))
return operation.ErrorCode;
}
return HResult.Ok;
}
首先,不清楚为什么将案例return HResult.Ok;
视为特殊情况:链中可能包含一个永不终止并返回该值的终止操作。这将使我们摆脱最后的if
。
第二,我们的职能仍然有两个责任:访问链并检查结果。
一个想法可能是将操作封装到一个真实的链中,因此我们的功能可以简化为:
return operations.ChainTogether(_check).Execute();
我们有2个选项:
我要继续讨论后者,但这是有争议的。我要介绍一个类,它对链中的环进行建模,使代码远离我们的类:
internal class OperationRing : IRing
{
private readonly Check _check;
private readonly IOperation _operation;
internal IRing Next { private get; set; }
public OperationRing(Check check, IOperation operation)
{
_check = check;
_operation = operation;
}
public HResult Execute()
{
var operationResult = _operation.DoJob();
if (_check.Succeeded(operationResult))
return Next.Execute();
return _operation.ErrorCode;
}
}
此类负责执行操作,并在下一个环成功执行时处理该环,或者中断返回正确错误代码的链。
该链将由永不失败的元素终止:
internal class AlwaysSucceeds : IRing
{
public HResult Execute() => HResult.Ok;
}
我们原来的课程减少为:
internal class SomeClass
{
private readonly Check _check;
private readonly List<IOperation> _operations;
public SomeClass(Check check, List<IOperation> operations)
{
_check = check;
_operations = operations;
}
internal HResult SomeFunction()
{
return _operations.ChainTogether(_check).Execute();
}
}
在这种情况下,ChainTogether()
是实现为List<IOperation>
的扩展的函数,因为我不认为链接逻辑是我们课程的责任。
将职责划分为最适当的类别是绝对有争议的。例如:
因此,我确定还有其他几种方法可以重构原始功能。在工作面试中,或在一对编程会议中,我希望会进行很多讨论和评估。
答案 1 :(得分:1)
你可以在这里使用早期回报。
function() {
if(!SUCCEEDED(Operation1())) {
return OPERATION1FAILED;
}
if(!SUCCEEDED(Operation2())) {
return OPERATION2FAILED;
}
if(!SUCCEEDED(Operation3())) {
return OPERATION3FAILED;
}
if(!SUCCEEDED(Operation4())) {
return OPERATION4FAILED;
}
# everything succeeded, do your thing
return S_OK;
}