C# 使用Extract方法进行重构,这总是一个好主意吗?
所以我花了10-20分钟重构了一个30行的方法。结果是:74行。在我看来那不好。当然,它可能更具可读性,但您仍然需要跳转到每个方法来了解细节。而且,提取所有这些方法让我很难找出它们的好名字。如果将来我在重构一个方法时,希望使用一个具有完全不同签名的现有方法名,会怎么样?读起来很难——至少我是这么想的 以下是重构前我的代码:C# 使用Extract方法进行重构,这总是一个好主意吗?,c#,refactoring,control-flow,C#,Refactoring,Control Flow,所以我花了10-20分钟重构了一个30行的方法。结果是:74行。在我看来那不好。当然,它可能更具可读性,但您仍然需要跳转到每个方法来了解细节。而且,提取所有这些方法让我很难找出它们的好名字。如果将来我在重构一个方法时,希望使用一个具有完全不同签名的现有方法名,会怎么样?读起来很难——至少我是这么想的 以下是重构前我的代码: public ActionResult Confirm(string id) { if (string.IsNullOrEmpty(id))
public ActionResult Confirm(string id)
{
if (string.IsNullOrEmpty(id))
{
if (! IsLoggedIn())
{
return RedirectToAction("Login");
}
if(User.User.Confirmed)
{
return RedirectToAction("Index");
}
return View("PendingConfirmation");
}
int parsedId;
if (!int.TryParse(id, out parsedId))
{
return Http(400, View("BadRequest", model: "EC2007: Could not parse int"));
}
return Try(() =>
{
UserBusinessLogic.ConfirmUser(parsedId);
_authentication.SetAuthCookie(parsedId.ToString(CultureInfo.InvariantCulture), true);
return RedirectToAction("Index");
}, (code, errorCode) => Http(code, GenericErrorView(null, null, errorCode)));
}
现在,这是重构版本:
/// <summary>
/// Confirms the specified id.
/// </summary>
/// <param name="id">The id.</param>
/// <returns></returns>
public ActionResult Confirm(string id)
{
int parsedId;
ActionResult actionResult;
if (! AssertConfirmConditions(id, out parsedId, out actionResult))
{
return actionResult;
}
return Try(() => InternalConfirmUser(parsedId), (code, errorCode) => Http(code, GenericErrorView(null, null, errorCode)));
}
private ActionResult InternalConfirmUser(int parsedId)
{
UserBusinessLogic.ConfirmUser(parsedId);
_authentication.SetAuthCookie(parsedId.ToString(CultureInfo.InvariantCulture), true);
return RedirectToAction("Index");
}
private bool AssertConfirmConditions(string id, out int parsedId, out ActionResult actionResult)
{
actionResult = null;
parsedId = 0;
return
! ShouldRedirectAwayFromConfirm(id, ref actionResult)
&& CanParseId(id, ref parsedId, ref actionResult);
}
private bool CanParseId(string id, ref int parsedId, ref ActionResult actionResult)
{
if (int.TryParse(id, out parsedId))
{
return true;
}
actionResult = Http(400, View("BadRequest", model: "EC2007: Could not parse int"));
return false;
}
private bool ShouldRedirectAwayFromConfirm(string id, ref ActionResult actionResult)
{
if (string.IsNullOrEmpty(id))
{
if (ShouldRedirectToLoginView(out actionResult)) return true;
if (ShouldRedirectToIndex(ref actionResult)) return true;
actionResult = View("PendingConfirmation");
return true;
}
return false;
}
private bool ShouldRedirectToIndex(ref ActionResult actionResult)
{
if (User.User.Confirmed)
{
actionResult = RedirectToAction("Index");
return true;
}
return false;
}
private bool ShouldRedirectToLoginView(out ActionResult actionResult)
{
actionResult = null;
if (! IsLoggedIn())
{
actionResult = RedirectToAction("Login");
return true;
}
return false;
}
//
///确认指定的id。
///
///身份证。
///
公共操作结果确认(字符串id)
{
int-parsed;
行动结果行动结果;
如果(!AssertConfigConditions(id、out parsedId、out actionResult))
{
返回操作结果;
}
返回Try(()=>InternalConfirmUser(parsedId),(code,errorCode)=>Http(code,GenericErrorView(null,null,errorCode));
}
私有ActionResult InternalConfirmUser(int-parsedId)
{
ConfirmUser(parsedId);
_authentication.SetAuthCookie(parsedId.ToString(CultureInfo.InvariantCulture)),true;
返回操作(“索引”);
}
私有布尔AssertConfigConditions(字符串id、out int parsedId、out ActionResult ActionResult)
{
actionResult=null;
parsedId=0;
返回
!ShouldRedirectAwayFromConfirm(id,ref actionResult)
&&CanParseId(id,ref parsedId,ref actionResult);
}
private bool CanParseId(字符串id,ref int parsedId,ref ActionResult ActionResult)
{
if(int.TryParse(id,out-parsedId))
{
返回true;
}
actionResult=Http(400,视图(“BadRequest”,模型:“EC2007:无法解析int”);
返回false;
}
private bool应重定向awayFromConfirm(字符串id,ref ActionResult ActionResult)
{
if(string.IsNullOrEmpty(id))
{
if(ShouldRedirectToLoginView(out actionResult))返回true;
if(ShouldRedirectToIndex(ref actionResult))返回true;
actionResult=视图(“待定确认”);
返回true;
}
返回false;
}
私有布尔值应重定向到索引(参考ActionResult ActionResult)
{
如果(用户。用户。确认)
{
actionResult=重定向到操作(“索引”);
返回true;
}
返回false;
}
private bool应重定向到LoginView(out ActionResult ActionResult)
{
actionResult=null;
如果(!IsLoggedIn())
{
actionResult=重定向到操作(“登录”);
返回true;
}
返回false;
}
老实说,我更喜欢第一个版本。我是不是遗漏了什么?当用几个控制流语句重构方法时,它会变得丑陋
我应该坚持使用非重构版本吗?这能做得更好吗
编辑:根据评论,我想指出我使用了ReSharper的Extract方法,我没有手动执行此操作。我一般认为,创建方法不应该太多地将代码分解成更小的部分,而是使功能的可重用性更容易和更易维护。如果一个30行长的方法中包含的任何代码都没有在方法内部或项目中的其他地方重用,那么我看不出它有什么错。当考虑是否将某个东西分解为它自己的方法时,询问它是否在任何时候都会被重用——如果它的逻辑不可能通过程序再次出现,那么就没有必要将它重构为它自己的方法(除非你遇到阅读或调试时间过长的情况)我认为你的重构让事情变得更糟 我的第一个想法是这样的:
public ActionResult Confirm(string id)
{
if (string.IsNullOrEmpty(id))
{
return HandleMissingId();
}
int parsedId;
if (!int.TryParse(id, out parsedId))
{
return Http(400, View("BadRequest", model: "EC2007: Could not parse int"));
}
return Try(() =>
{
ConfirmUser(parseId);
return RedirectToAction("Index");
}, ShowGenericError);
}
private void ConfirmUser(int userId)
{
UserBusinessLogic.ConfirmUser(userId);
_authentication.SetAuthCookie(userId.ToString(CultureInfo.InvariantCulture), true);
}
private ShowGenericError(int code, int errorCode)
{
return Http(code, GenericErrorView(null, null, errorCode));
}
private ActionResult HandleMissingId()
{
if (! IsLoggedIn())
{
return RedirectToAction("Login");
}
if(User.User.Confirmed)
{
return RedirectToAction("Index");
}
return View("PendingConfirmation");
}
这种方法提取的方法封装了其他方法可能非常需要的特定概念/功能。似乎更像是一个问题。我不明白你为什么要重构这样的线性短方法。这是一个坏主意。如果至少你想保持干爽(不要重复你自己),但您没有告诉我们您可以从某个地方删除一些重复的代码。请暂时同意Adam的说法(只要您不使用从其他地方提取的方法)KISS和YAGNI似乎是应该应用的原则。我在这一点上支持Xanatos。没有必要根据您在这里展示的内容在原始方法中重构任何东西。CanParseId是重构走得太远的完美例子。您采用了一个简单的过程,并包含了可怕的
ref
变量。@Logarr和其他-t谢谢,这正是我想听到的。我试图遵循每个方法只有一个缩进和每个方法5行的编码规则-非常难用“复杂”的逻辑。我同意-事实上非常同意-但有时我会偶然发现500行方法(是的,500,恶心)这不包含任何可重用的内容。谢谢,是的,这是一个更干净的方法。事实上,HandleMissingId是该方法的一个好名字,但我有很多情况下,我想处理缺少的ID,但不总是返回PendingConfirmation视图。我应该输出一个布尔值,以确定我是否应该重定向到该视图?-所以重构就是,如果你只需要一次,就不要提取方法?@Jeff:不,一般的想法是提取有意义的、自包含的部分。你的方法既不是:-),也不是,你通常不应该使用out
或ref
参数。相反,请将您要重定向到的视图的名称作为参数传递。@Jeff:这两个示例完全不同,如果它们是唯一的示例,则可以使用不同的方法