谁负责检查数据的有效性?

我很困惑,究竟是调用者还是被调用者负责检查数据的合法性。

被调用者是否应该检查传入的参数是否应该不为null并满足其他一些要求,以便被调用方法能够正常执行并成功执行,并捕获任何潜在的exception? 或者这是做主的责任呢?

消费者端(客户端) 提供者端(API)validation。

客户应该这样做,因为这意味着更好的体验。 例如,为什么networking往返只是被告知你有一个坏的文本字段?

提供商应该这样做,因为他们不应该信任客户(例如,XSS和中间人攻击)。 你怎么知道这个请求没被拦截? validation一切。

有几个级别的有效

  1. 所有必填字段,正确的格式。 这是客户端validation的内容。
  2. #1加上字段之间的有效关系(例如,如果X存在,那么Y是必需的)。
  3. #1加2号加业务有效:符合所有业务规则的正确处理。

只有提供者可以做#2和#3。

对于API,被调用者应该始终进行适当的validation,并为无效数据抛出一个描述性的exception。

对于任何客户端与IO开销客户端应该做基本validation以及…

validation:主叫与被叫

TLDR版本是两个。

长版本涉及谁,为什么,何时,如何和什么。

双方都应该准备回答“这个数据可以可靠地运行吗? 我们是否足够了解这些数据来做一些有意义的事情呢? 许多人会认为数据的可靠性永远不会被信任,但这只会导致鸡和鸡蛋的问题。 从两端追逐它不会提供有意义的价值,但在某种程度上它是至关重要的。

两者都必须validation数据的形状以确保基础可用性。 如果任何人不能识别或理解数据的形状,就没有办法知道如何进一步处理它的可靠性。 取决于环境,数据可能需要是特定的“types”,这通常是validation形状的简单方法。 我们经常会考虑types,这些types会将一些常见的行为证据回溯到某个特定的祖先,并且保留具有正确形状的关键特征。 如果数据不是内存结构,其他特性可能很重要,例如,如果它是一个stream或运行上下文之外的其他资源。

许多语言通过types或接口检查将数据形状检查作为内置语言function。 然而,当偏好构成而不是inheritance时,提供一种validation特征存在的良好机制是实施者的责任。 实现这一目标的一个策略是通过dynamic规划,或者特别是通过types反思,推理或反思。

被调用者必须validation它将运行的给定上下文的域(input集合)。 被调用的devise总是暗示它只能处理这么多的input案例。 通常这些值被分解成特定的子类或input类别。 我们validation被调用的域,因为调用与本地化的约束是密切相关的。 它比任何人都知道什么是好的input,什么不是。

  • 正常值 :这些域的值映射到一个范围。 对于每一个foo ,只有一个bar

  • 超出范围/超出范围值 :这些值是通用域的一部分,但不会映射到被调用的上下文中的范围。 这些值没有定义的行为,因此没有有效的输出是可能的。 经常超出范围的检查需要范围,限制或允许的字符(或数字,或合成值)。 基数检查(多重性)和随后的存在检查(空或空)是范围检查的特殊forms。

  • 导致不合逻辑或未定义行为的值:这些值是特殊值或边界情况,否则是正常的,但是由于algorithmdevise和已知的环境约束,会产生意想不到的结果。 例如,一个以数字为基础的函数应该防止零或溢出的累加器的分裂或意外的精度损失。 有时操作环境或编译器会警告这些情况可能发生,但依赖运行时或编译器不是好的做法,因为它可能并不总是能够推断出什么是可能的,哪些不是。 这个阶段应该在很大程度上validation,通过二次validation,调用者提供了良好的,有用的,有意义的input。

呼叫者

来电显得特别。 调用者有两种情况需要validation数据。

第一种情况是分配或明确的状态变化,其中通过某种显式机制在内部或外部由其容器中的某事物发生至less一个数据元素的变化。 这有些超出了问题的范围,但是要记住。 重要的是在发生状态变化时考虑上下文,并描述状态的一个或多个元素受到影响。

  • 自我/参照完整性 :如果其他参与者可以引用数据,则考虑使用内部机制来validation状态。 当数据没有一致性检查时,只能假设它处于不确定状态。 这不是中间的,而是不确定的。 了解你自己。 当你不使用一种机制来validation状态变化的内部一致性时,那么数据是不可靠的,并在第二种情况下导致问题。 确保调用者的数据处于已知的良好状态; 或者以已知的转换/恢复状态。 准备好之前不要打电话。

第二种情况是数据调用函数的时候。 一个来电者只能从被叫方那里预料到很多。 呼叫者必须知道并尊重被叫只能识别某个域名。 主叫方也必须是自利的,因为在被叫完成之后它可能会持续很久。 这意味着调用者必须帮助被调用者不仅成功,而且还适合于任务:不良数据产生不良数据。 同样的道理,就呼叫方而言,即使是关于被呼叫方的好的数据也可能不适合接下来的事情。 好的数据可能实际上是对于调用者来说不好的数据。 被叫方的输出可能会使主叫方当前状态的主叫方无效。

好的,足够的评论,一个呼叫者应该具体validation什么?

  • 逻辑和正常 :给出数据,是否被称为符合目的和意图的好策略? 如果我们知道它会失败并带有一定的价值,那么大多数时候没有适当的警卫来执行这个呼叫就毫无意义。 如果我们知道被调用者不能处理零,就不要问这个问题,因为它永远不会成功。 什么是更昂贵和更难以pipe理:一个[冗余(我们知道吗?)]保护条款,或一个例外[发生在一个可能长时间运行,外部可用的资源依赖进程晚]? 实现可以改变,并突然改变。 为呼叫者提供保护可减less改变该实施的影响和风险。

  • 返回值 :检查不成功完成。 这是一个呼叫者可能或可能不需要做的事情。 在使用或依赖返回的数据之前,如果系统devise包含可能伴随实际返回值的成功和失败值,则检查替代结果。

脚注:如果不清楚的话。 空是域问题。 它可能是也可能不是合乎逻辑的正常,所以要看情况。 如果null是函数的自然input,并且该函数可以合理地期望产生有意义的输出,则将其留给调用者使用。 如果调用者的域是这样的,那么null就不合逻辑,那么在两个地方都要防范它。

一个重要的问题是:如果你将null传递给被调用者,而被调用者正在产生某种东西,那么这不是一种隐藏的创build模式,是从什么地方创造出来的?

这完全是关于“合同”。 这是一个决定哪些参数是否正确的被调用者。 你可能会把文档中的“null”参数无效,然后抛出NullPointerExceptionInvalidArgumentException罚款。

如果返回结果为空参数有意义 – 在文档中说明它。 通常情况下,这种情况是一个糟糕的devise – 用更less的参数创build一个覆盖方法,而不是接受null。

只记得抛出描述性例外。 根据经验:

  1. 如果调用者传递的错误参数与文档中描述的不同(例如,null,id <0等),则抛出未经检查的exception( NullPointerExceptionInvalidArgumentException
  2. 如果调用者传递了正确的参数,但可能有一个预期的商业案例,使得无法处理该调用 – 您可能想要抛出一个检查的描述性exception。 例如 – 对于getPermissionsForUser(Integer userId) ,调用者传递userId不知道这个用户是否存在,但是它是一个非空的Integer。 您的方法可能会返回一个权限列表或索引一个UserNotFoundException 。 这可能是一个检查的exception。
  3. 如果参数是正确的根据文件,但他们导致处理内部错误 – 你可能会抛出一个未经检查的exception。 这通常意味着你的方法没有很好的testing;-)

这得看情况。

如果您可以确定如何处理您的被调用者中的无效数据,那么在那里做。

如果你不确定(例如,因为你的方法是相当一般的,并在几个不同的地方和方式使用),然后让主叫方决定。

例如,设想一个DAO方法,它必须检索某个实体,但没有find它。 你可以决定是否抛出一个exception,也许回滚一个事务,或者只是考虑一下吧? 在这样的情况下,决定如何处理它是绝对由呼叫者决定的。

都。 这是一个双方都很好的软件开发,与环境(C / S,Web,内部API)和语言无关。

被调用者应该根据logging良好的参数列表validation所有的参数(你没有logging它,对吗?)。 根据环境和体系结构,应该执行良好的错误消息或例外,以清楚地指示参数错误。

调用者应该确保只有适当的参数值在api调用中传递。 应尽快捕获任何无效值,并以某种方式反映给用户。

正如生活中经常发生的一样,双方都不应该假定对方会做正确的事情,而忽略潜在的问题。

我将对这个问题采取不同的观点。 在一个包含的应用程序中工作,调用者和被调用者都在相同的代码中。 然后,被叫方的合同所要求的任何validation都应该由被叫方完成。

所以你写了一个函数,你的合同说“不接受NULL值”。 你应该检查NULL值没有被发送,并引发错误。 这可以确保你的代码是正确的,如果别人的代码正在做某件事情,他们不应该早点知道它。

此外,如果您认为其他代码会正确调用您的方法,而他们不这样做, 则会使追踪潜在错误的来源更加困难。

这对于“尽早失败,失败”的关键在于一旦发现问题就会引发错误。

取决于你在名义上,防守上还是全面上进行编程。

  • 如果你防御性地编程(对于大多数Java方法我个人最喜欢),你在方法中validationinput。 如果validation失败,则抛出exception(或以其他方式失败)。
  • 如果你名义上编程,你不validationinput(但期望客户端确保input有效)。 当validation会对性能产生负面影响时,此方法非常有用,因为validation需要花费大量时间(如耗时的search)。
  • 如果你完全编程(我个人最喜欢的是大多数Objective-C方法),你在方法中validationinput,但是把无效input改为有效input(就像把值alignment到最近的有效值一样)。

在大多数情况下,您可以防守(快速失败)或完全(失败)编程。 名义编程是危险的国际海事组织,应该避免在期望从外部来源input。

当然,不要忘记logging一切(特别是在名义编程时)。

validation数据是被调用者的责任 。 这是因为只有被调用者知道什么是有效的。 这也是一个很好的安全实践。

它需要在客户端和服务器(被叫方和主叫方)两端。

客户:

  1. 这是最有效的。
  2. 客户端validation将减less一个请求到服务器。
  3. 减less带宽stream量。
  4. 时间虚耗(如果它有延迟从服务器响应)

服务器:

  1. 不相信UI数据(由于黑客)。
  2. 大多数后端代码将被重用,所以我们不知道数据是否为空等。 所以我们需要在callee和caler方法上进行validation。

总的来说,1.如果数据来自UI,那么在UI层validation总是更好的,并且在服务器层进行双重检查。 2.如果数据传输在服务器层本身,我们需要对被调用者进行validation并进行双重检查,我们也需要在调用者端进行。

谢谢

在我的拙见中,再用几句话来解释为什么,大多数时候这是被调用者的责任,但这并不意味着调用者总是毫无瑕疵。

原因是被调查者处于最好的位置,知道自己需要做什么工作,因为这是做这项工作的人。 因此,对象或方法是自我validation的良好封装。 如果被调用者不能在空指针上工作,这是一个无效的参数,应该被抛出。 如果有争论超出范围,这也很容易防范。

但是,“无知的法律不是辩护”。 对于一个调用者来说,把所有的东西都放到它的帮助器函数中,让被调用者把它整理出来,这不是一个好的模式。 当调用者这样做时,调用者不会添加任何值,特别是如果调用者向被调用者推送的数据是由其自己的调用者自己给出的数据,这意味着调用堆栈的这一层可能是多余的。 这也使得调用者和被调用者的代码非常复杂,因为双方“防御”了另一方(被调用方抢救可行和testing所有东西)的不良行为,调用方将调用包装在尝试catch语句中,改正呼叫)。

因此,调用者应该validation对传递数据的要求可以知道什么。 在进行调用时存在固有的时间开销,例如调用服务代理时,尤其如此。 如果你不得不等待很长一段时间才能发现你的参数是错误的,那么在同一个客户端需要花费几滴时间,这个优势就显而易见了。 被调用者的守卫条款正是这样; 最后一道防线和优雅的失败之前,丑陋的东西被抛出实际的工作程序。

调用者和被调用者之间应该有一个叫做契约的东西。 如果input数据在指定的值,被调用者确保它做对了。 他还应该根据这些规范检查收集的数据是否正确。 在Java中,你可以抛出一个InvalidArgumentException

来电者也应该在合同规范内工作。 如果他应该检查他交出的数据取决于案件。 理想情况下,您应该以不需要检查的方式来编程调用者,因为您确信数据是有效的。 如果是例如用户input,则不能确定它是否有效。 在这种情况下,你应该检查它。 如果你不检查它,你至less必须处理例外,并作出相应的反应。

被调用者有责任检查它收到的数据是否有效。 不执行这项任务几乎肯定会导致不可靠的软件,并使您面临潜在的安全问题。

话虽如此,如果你有控制客户端(调用者)的代码,那么你也应该至less执行一些validation,因为它会导致更好的总体经验。

一般情况下,尽可能早地尝试解决数据问题,这样可以减less更多的麻烦。