如何避免代码中自定义日志消息的复杂逻辑?

时间:2013-09-03 19:51:36

标签: c# .net logging solid-principles single-responsibility-principle

我知道标题有点过于宽泛,但我想知道如何避免(如果可能的话)我刚刚在我们的解决方案上编码的这段代码。

当此代码导致日志信息不足时,问题就出现了:

...
var users = [someRemotingProxy].GetUsers([someCriteria]);
try
{
    var user = users.Single();
}
catch (InvalidOperationException)
{
    logger.WarnFormat("Either there are no users corresponding to the search or there are multiple users matching the same criteria.");
    return;
}
...

我们在我们的模块中有一个业务逻辑,需要一个符合某些标准的“用户”。事实证明,当问题出现时,这个小的“不确定”的信息不足以让我们正确地知道发生了什么,所以我编码了这个方法:

private User GetMappedUser([searchCriteria])
{
    var users = [remotingProxy]
           .GetUsers([searchCriteria])
           .ToList();

    switch (users.Count())
    {
        case 0:
            log.Warn("No user exists with [searchCriteria]");
            return null;

        case 1:
            return users.Single();

        default:
            log.WarnFormat("{0} users [{1}] have been found"
                          users.Count(), 
                          String.Join(", ", users);
            return null;
    }

然后从主代码中调用它:

...
var user = GetMappedUser([searchCriteria]);
if (user == null) return;
...

我看到的第一个奇怪的事情就是列表上switch的{​​{1}}语句。起初这看起来很奇怪,但不知何故最终成为更清洁的解决方案。我试图在这里避免异常,因为这些条件非常正常,我听说尝试使用异常来控制程序流而不是报告实际错误是不好的。代码之前从Single抛出.Count(),所以这更像是一个重构。

这个看似简单的问题还有另一种方法吗?它似乎是一种Single Responsibility Principle违规行为,日志介于代码和所有内容之间,但我没有看到一个体面或优雅的方式。在我们的情况下情况更糟,因为相同的步骤重复两次,一次是“用户”,然后是“设备”,如下所示:

  1. 获取唯一身份用户
  2. 获取独特用户的唯一设备
  3. 对于这两项操作,我们必须确切知道发生了什么,返回了哪些用户/设备不是唯一的,等等。

2 个答案:

答案 0 :(得分:1)

@AntP找到了我最喜欢的答案。我认为你挣扎的原因是你实际上有两个问题。首先,代码似乎有太多的责任。应用这个简单的测试:给这个方法一个简单的名称来描述它所做的一切。如果你的名字包含“和”这个词,那就太多了。当我应用该测试时,我可能将其命名为“GetUsersByCriteriaAndValidateOnlyOneUserMatches()”。所以它做了两件事。将其拆分为不关心返回多少用户的查找函数,以及评估关于“我只能处理一个用户返回”的业务规则的单独函数。

但是你仍然有原始问题,这就是switch语句在这里看起来很尴尬。在查看switch语句时会想到策略模式,虽然实际上我认为在这种情况下它有点过分。

如果你想探索它,可以考虑创建一个基础“UserSearchResponseHandler”类,以及三个子类:NoUsersReturned; MultipleUsersReturned;和OneUserReturned。它将有一个工厂方法接受一个用户列表并根据用户数量返回一个UserSearchResponseHandler(在工厂内部封装开关的逻辑。)每个处理程序方法都会做正确的事:记录适当的东西然后返回null ,或返回一个用户。

战略模式的主要优势在于您对其识别的数据有多种需求。如果您的代码中隐藏了切换语句,这些语句都依赖于搜索找到的用户数,那么这将是非常合适的。工厂还可以封装更复杂的规则,例如“user.count must = 1 AND user [0] .level must = 42 AND它必须是9月的星期二”。您还可以真正了解工厂并使用注册表,从而允许对逻辑进行动态更改。最后,工厂很好地将业务规则的“解释”与规则的“处理”区分开来。

但在你的情况下,可能不是那么多。我猜你可能只有这一规则的一次出现,它看起来很静态,而且它已经恰当地位于你获得它正在验证的信息的位置附近。虽然我仍然建议从响应解析器中拆分搜索,但我可能只是使用切换。

考虑它的另一种方式是使用一些Goldilocks测试。如果这确实是一个错误的情况,你甚至可以抛出:

if (users.count() < 1)
{
    throw TooFewUsersReturnedError;
}

if (users.count() > 1)
{
    throw TooManyUsersReturnedError;
}

return users[0];  // just right

答案 1 :(得分:0)

这样的事情怎么样?

public class UserResult
{
    public string Warning { get; set; }
    public IEnumerable<User> Result { get; set; }
}

public UserResult GetMappedUsers(/* params */) { }

public void Whatever()
{
    var users = GetMappedUsers(/* params */);
    if (!String.IsNullOrEmpty(users.Warning))
        log.Warn(users.Warning);
}

如果需要,请切换List<string> Warnings。这会将您的GetMappedUsers方法更像是一个返回一些数据和一些结果元数据的服务,它允许您将日志记录委派给调用者 - 它所属的位置 - 因此您的数据访问代码可以继续执行它的工作。

虽然,老实说,在这种情况下,我更希望只返回GetMappedUsers的用户ID列表,然后使用users.Count来评估调用方中的“案例”并根据需要进行记录