【问题标题】:Tell, Don't Ask Principle and Password Expiration告诉,不要问原则和密码过期
【发布时间】:2012-03-26 19:47:04
【问题描述】:

为了保持务实的编程原则,我试图根据“告诉,不要问”原则来决定如何处理用户密码更改。

我有一个用户对象,其密码每 30 天过期一次。如果密码过期,我需要能够显示密码过期/更改密码视图。询问对象密码是否过期(它的状态)然后选择显示哪个视图似乎违反了原则。

处理这种情况的最佳方法是什么?

【问题讨论】:

  • 密码过期是一件愚蠢的事情。人们只是倾向于使用蹩脚的,因为他们只是在必须再次更改复杂的时设法记住它。
  • 您不同意 PCI 合规性
  • @Hupperware 表示:-p

标签: c# asp.net-mvc law-of-demeter tell-dont-ask


【解决方案1】:
login
   model.validate();
   return model.show(self);

passwordExpired()
  return View("ChangePassword")

loginSuccess()
  return View("default")

class User
  show(aController)
      if passwordExpired
          return aContoller.passwordExpired()
     else return aContoller.loginSuccess()

告诉,不要问,没有例外,它遵守得墨忒耳法则

【讨论】:

    【解决方案2】:

    当密码通过身份验证时,您可以从用户对象中抛出 PasswordExpired 异常,或者您首先对用户调用的任何函数。

    【讨论】:

    • 但这违反了另一个原则——不要使用异常来控制流程。密码过期并非例外情况。
    • 你不应该在控制流中使用异常——它们很昂贵
    • 好点,谢谢。然而,如果你想坚持“告诉,不要问”,你怎么能毫无例外地处理这个问题?从函数返回一个状态?
    • 不是很特别吗?您通常不会期望密码在您的正常程序流程中过期。
    • 不是。这是您在使用应用程序、使用适当的数据和适当的流程时会遇到的问题。异常意味着发生了错误,而不仅仅是“快乐轨道”之外的事情。
    【解决方案3】:

    您应该考虑让用户对象有一个提供布尔值的 Validate() 方法(就像会员提供者合同一样),或者考虑让 Validate() 方法返回某种指示验证结果的枚举(好的,INVALID_PASSWORD、EXPIRED_PASSWORD 等)。

    有很多选择——如果密码过期,抛出异常不应该是其中之一。这是一种糟糕的形式,并且由于运行时必须展开堆栈,因此也会影响性能。

    【讨论】:

    • 我从来不理解这个论点...堆栈非常擅长自行展开。这就是它的本意。
    • 做一个测试。编写一个抛出异常并将输出写入控制台的控制台应用程序。在调试模式下运行它。注意你的异常被抛出和显示需要多长时间。当你抛出一个异常时会发生很多事情——它很昂贵。我也明白调试也比在发布模式下运行要慢,但是抛出异常的时候还是有很多事情发生的。
    • 那很好...我会在异常对象出现时处理它,因为它包含丰富的信息,所以我将处于处理该问题的最佳位置。我真的不在乎是否需要额外的几纳秒来构建。
    • 谁在乎调试模式需要多长时间。当性能很重要时,您不会在紧密的循环中使用异常,但是如果您担心在登录失败等情况下单个异常的成本,那么您正在遭受过早优化综合症,克服它,异常是好的,直到分析器说它们不是。 Lisp 和 Smalltalk 调用异常通知和条件。异常是通知的子类,即它们对非异常情况下的控制流很有用。除非分析器这样说,否则出于性能原因避免异常是不正确的。
    • 为密码过期抛出异常是不错的形式,顺便说一句,这是一种异常情况,即它不是主要的成功路径。捕获 InvalidPassword 或 ExpiredPassword 并呈现适当的视图,异常在这里工作得很好,很优雅,并且不会降低性能。在发布模式下抛出单个异常所花费的时间根本不值得考虑,因为它需要花费数个数量级的时间才能访问数据库并验证密码。
    【解决方案4】:

    我个人不喜欢编写 arround 返回值/Enum 类型。您拥有的返回类型越多,您必须测试/使用的路径就越多。此外,使用异常来控制流程是一种不好的做法(除非您真的找不到任何其他选择 - 但通常有更好的选择)。

    一个过期的密码对我来说并不是什么特别的事情。毕竟它是一个有效的状态(否则你会做一些事情来防止密码过期)

    我尽量保持简单,要么返回 bool 或类似 Func<T> 的东西,可以由调用者直接调用。

    大概是这样的:

    public class User
        {
            private DateTime _lastChangeDate;
            public Action Validate()
            {
                if (_lastChangeDate >= DateTime.Now.AddDays(-30))
                {
                    return new Action(() => this.Login());
                }
                else
                {
                    return new Action(() => this.ChangePassword());
                }
            }
            private void Login()
            {
                Console.WriteLine("Login");
            }
            private void ChangePassword()
            {
                Console.WriteLine("Change Password");
            }
        }
    

    在调用方:

    user.Validate().Invoke();
    

    【讨论】:

      【解决方案5】:

      解决这个问题的一种方法是像这样的面向对象建模:

      public class Login {
      
      private String userName;
      private String password;
      private Date expirationDate;    
      
      public void authenticate(String password) {
          if (this.password.equals(password) {
              redirectoToMainOrEditPage();
          } else {
              redirectToFailPage();
          }
      }
      
      private void redirectToMainOrEditPage() {
          Date today = new Date();
      
          if (today.before(expirationDate)) {
              redirectToMainPage();
          } else {
              redirectToEditPage();
          }
      }
      
      private void redirectToMainPage() {
          ...
      }
      
      private void redirectToEditPage() {
          ...
      }
      
      private void redirectToFailPage() {
          ...
      }
      
      public void changePassword(String newPassword) {
          ...
      }
      
      public void changeExpirationDate(Date newDate) {
          ...
      }
      }
      

      这样您就不会向其他域对象询问任何内容,而是告诉 Login 进行身份验证,因为它拥有执行此操作所需的一切。

      【讨论】:

        猜你喜欢
        • 2011-03-17
        • 2013-07-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-11
        • 1970-01-01
        • 1970-01-01
        • 2017-10-14
        相关资源
        最近更新 更多