3

首先,我不确定该主题是否属于 StackOverflow。但是,我发现了一些关于其他编程语言的讨论,这些讨论解决了 StackOverflow 上的主题,但不适用于 Javascript。所以,我只想把我的问题扔进戒指:

在 Javascript 中假设的 GetUserByUsername 函数中处理可能的“未找到”场景的最佳方式/模式是什么。

我为什么要问?我想在整个项目中遵循某种编码标准。

我更具体的情况如下:

  • 我有班级用户
  • 我有一个用户列表,其中包含用户类的实例
  • 我查找某个用户,但该用户在列表中不存在

我应该如何处理我的相关功能中的“找不到”情况?

我发现了以下方法:

  • 返回未定义
  • RORO(接收对象,返回对象)
  • 空对象模式
  • 抛出自定义错误

那我到底是什么意思。以下面的示例类用户为例。

module.exports = class User {
  constructor() {   
    this.userName = null,
    this.name = null,
    this.age = null
  }
  ...
  getName() {
    console.log(this.name);
  }
}

在我的代码中的某处,我有一个函数,它提供了从用户列表中按用户名返回某个用户的功能。该列表是一个数组,由 User 类的实例组成。

一个简单的实现可能是这样的:

{
  ...

  getUserByUsername(pUserName) {
    /* listOfUsers array with objects of the class User */
    var lUser = this.listOfUsers.find(user => user.userName === pUserName);
    return lUser;
  }
  ...
}

在某些情况下,传递的用户名可能不属于某个用户。因此找不到用户。

我怎么能处理它?

1.)返回未定义 - 就像示例代码一样。

/* We expect "Unknown" is not a user name in that list*/
var lRequestedUser = getUserByUsername("Unknown");

/* Check if requested user is undefined. Note: I'm using lodash for staff like that */
if(_.undefined(lRequestedUser)) {
  //Handling 
}

lRequestedUser.getName();

2.)RORO(接收一个对象,返回一个对象)我将一个对象传递给我的函数,并返回一个对象作为结果。在我要返回的对象中,我有一个指示器显示操作是否成功。

getUserByUsername(pParams) {
  if(_.undefined(pParams.userName)) {
    //Throw custom error 
  }

  var lReturnObject = {
    isSuccessfull: false,
    data: null
  }

  /* listOfUsers array with objects of the class User */
  var lUser = this.listOfUsers.find(user => user.userName === pParams.userName);

  if(!(_.undefined(lUser))) {
    lReturnObject.isSuccessfull = true;
    lReturnObject.data = lUser
  }
  return lUser;
}
...
}

/* 
Building a object which is passed to the function. Username is a member of that object
 */
var lRequest = {
  userName: "Unknown"
}

/* We expect "Unknown" is not a user name in that list*/
var lRequestedUser = getUserByUsername(lRequest);

if(!(lRequestedUser.isSuccessfull)) {
//Handling 
}

lRequestedUser.data.getName();

3.) 空对象模式 我不喜欢该解决方案的事实是,如果主类获得附加功能,我总是必须增强空对象的类。

  module.exports = class NullUser {
      constructor() {   
        this.userName = null,
        this.name = null,
        this.age = null
      }
      ...
      getName() {
        console.log(this.name);
      }
    }


{
  ...

  getUserByUsername(pUserName) {
    /* listOfUsers array with objects of the class User */
    var lUser = this.listOfUsers.find(user => user.userName === pUserName);

    if(_.undefined(lUser)) {
      return new NullUser();
    }

    return lUser;
  }
  ...
}


/* We expect "Unknown" is not a user name in that list*/
var lRequestedUser = getUserByUsername("Unknown");
lRequestedUser.getName();

4.) 抛出自定义错误

{
  ...
  getUserByUsername(pUserName) {
    /* listOfUsers array with objects of the class User */
    var lUser = this.listOfUsers.find(user => user.userName === pUserName);

    if(_.undefined(lUser)) {
      throw new CustomError(...);
    }

    return lUser;
  }
  ...
}

/* We expect "Unknown" is not a user name in that list*/
try {
  var lRequestedUser = getUserByUsername("Unknown");
  lRequestedUser.getName();
} catch (e) {
  if (e instanceof CustomError) {
      //Handling
  }
}

就我个人而言,我更喜欢返回包含指标的对象的选项,以及引发自定义错误的选项。该示例是同步的。在我的项目中,我也有异步代码。我正在为此使用承诺。

那么有什么想法是“最好的”解决方案或我应该喜欢的方式吗?

4

2 回答 2

1

就个人而言,我会推荐#1。有几个原因:

  1. 它反映了其他原语的行为(访问数组/哈希的元素、结果find等),因此其他 Javascript 开发人员将熟悉该模式。
  2. 响应本身本质上是错误的,这使得非常简洁地检查“未找到”情况变得微不足道。在这种情况下,由于您期望一个对象(而不是数字,其中 0 是错误的),因此这是一个不错的模式。
  3. 空对象模式比较麻烦。无论如何,用户必须知道如何检查对象是否存在“未找到”情况,并且该检查将非常具体到您如何实现空对象。最好使用语言原语来表达什么都不存在。
  4. 我不认为 RORO 与 Null 对象有很大不同。用户仍然必须知道如何检查响应,并且 RORO 暗示返回值本身不是可用的东西......相反,他们正在寻找的实际响应嵌入在响应中的某个位置。
  5. 例外应该用于真正的“例外”情况。列表不包含您搜索的内容是完全合理的,因此这并不是一个例外。此外,异常需要更多代码来处理。它们对某些事情有好处,但这真的不是其中之一。
于 2018-09-01T02:12:10.193 回答
1

答案是,在我看来,这取决于!您提到的所有方法都有特定的用例,它们中的每一个都占主导地位。让我们一一来看。(最后是批评家)

带有回调的异步应用

尽管回调被认为是旧的和不流行的,但它们为我们提供了我所谓的“多个返回值”的灵活性。

getUserByName(name, callback) {
    // Caller can check the `err` and `status` to decide what to do next. 
    return callback(err, status, user);
}
  • 在这里,抛出错误将无法按预期工作。也就是说,您无法使用 try-catch 捕获抛出的错误。您当然可以“返回”自定义错误。但这似乎毫无意义,能够单独返回“状态”。
  • status使用附加参数返回空对象或 RORO 没有意义。不过,您可能更愿意摆脱对status空对象或 RORO 的支持。

带有 promises/async-await 的异步应用

在这里,抛出错误将起作用。如果你使用 async-await,try-catch 的 catch 会报错。或者,如果您按原样使用 Promise,则 Promise 链的捕获将得到它。

同步应用

抛出错误并使用 try-catch 按预期工作。但是,这在使用任何 3rd 方服务的现实世界应用程序中将是罕见的情况。

但是,毕竟使用异常存在问题。是否要将“无数据”场景视为错误取决于业务逻辑。

  1. 如果您只想告诉最终用户,没有这样的用户,那么使用异常是有意义的。
  2. 如果您想继续创建一个新用户,那么它可能没有意义。您需要明确区分错误是由于“无数据”,还是由于“驱动程序错误”、“超时”等。根据我的经验,使用这样的异常很容易引入错误。

因此,该问题的解决方案将是使用自定义错误。话又说回来,在被调用者和调用者中会有太多的样板代码。这甚至可能导致逻辑语义的混淆。另一方面,过多地使用自定义错误会导致不一致问题,并且随着时间的推移可能会导致应用程序难以维护。(这完全取决于应用程序如何发展。例如,如果开发人员经常更改,不同的人会对这些自定义错误应该是什么有不同的看法)


有意见的答案

看过好的应用程序和高度不可维护的巨石之后,我会给你我个人的偏好。但请记住,这可能不是最终的解决方案。

注意:这个(自以为是的)解决方案基于以下事实,

  1. 所需的样板代码数量
  2. 代码语义
  3. 新开发人员开始开发应用程序的便利性
  4. 可维护性

不要使用错误来处理无数据情况。相反,明确检查“空”数据并单独处理案例。这里我将RORO模式,略有不同。我确实返回了一个对象,但接收的是参数而不是对象。这取决于参数的数量。如果参数的数量太多,那么接收对象可能是有意义的。但在我看来,这可能表明一个糟糕的业务/架构决策

考虑以下用户登录示例。

  1. 如果登录时发生错误,我会向最终用户显示“错误”状态。
  2. 如果成功获取用户,我将用户登录
  3. 如果用户不在那里,我创建一个新用户。(假设我有足够的数据来自它自己的请求)

一个伪代码可能看起来像这样,

// Controller code
async signIn(name, email) {
  try {
    let user = await userDao.getUserByUserName(name);
    if (!user.exists) { 
       user = await userDao.createUser(name, email);
    }
    return user; 
  } catch(e) {
    // handle error
  }
}

或者像这样,

    // Controller code
async signIn(name, email) {
  try {
    let result = await userDao.getUserByUserName(name);
    if (!result.user) { 
       result = await userDao.createUser(name, email);
    }
    return result.user; 
  } catch(e) {
    // handle error
  }
}

使用自定义错误的一个很好的例子是区分错误是在检索用户时发生还是在创建用户时发生。

status使用(作为回调的参数,或作为返回对象的属性)的主要好处是,您可以使用它将应用程序的其余部分设计为状态机(无数据场景仅一种这样的状态。在复杂的业务中可以有许多状态,基于用户类型、部分检索等)。

于 2018-09-01T02:02:00.157 回答