在其中, 失败并非意外或者错误, 而是程序行为的一部分, 多态不但体现在成功路径上的可替换特性, 更展现在失败路径的可预测以及可处理方面, 理解失败的结构化语义, 是掌握面向对象设计、构建健壮系统的关键所在。
7.1 失败作为正常分支
于诸多传统的面向对象设计里, “失败”常常被认定为是那种需加以避免或者予以隐藏的状况。
不过话说回来, 于那种实践的具体情境当中, 失败被看作是跟成功一块儿并存着的, 并且是同样具备可靠性的行为类别分支。
value = None # 正常处理失败表达式 天然包含两条合法路径:
• 键存在:返回对应值
• 键不存在 :抛出
失败不是隐藏的意外,而是调用方必须正视并处理的正常结果。
失败路径的存在,使接口语义更加完整,而不是更加脆弱。
7.2 的异常语义
通过异常机制,为失败路径提供了明确且可区分的语义表达:
print("属性不存在")诸多示例呈现出, 怎样借由异常类型, 给不一样的失败缘由予以精准语义。
异常并非仅仅向调用方传达“失败了”这一信息, 反而回应了更为关键的问题, 即!失败是以怎样的方式发生的,属于哪一类别, 以及是否能够恢复。
所以, 于其中, 异常乃一种具备结构化特征的失败返回机制, 并非单纯的错误信号。
异常类型本身,已经成为接口对失败方式的正式承诺。
7.3 多态中的失败一致性
有多态的语境存在, 对象之间具备的可替换性, 不仅在成功的路径情形中得以体现, 在失败路径的状况里, 也同样是能够被体现出来的。
要是不同的实现针对失败所采用的表达方式并非保持一致, 这么一来, 这种多态仅仅是在“顺利的状况之下”才会成立, 然而一旦进入到异常的分支, 便会出现崩解的情况。
在这个使用语境中,调用方已经隐式定义了接口的失败语义:
• I/O 相关问题应以 及其子类表达
• 接口不满足应以 表达
只要实现遵守这一失败约定,就可以被安全地替换使用。
(1)行为一致的失败实现
尽管 与 的内部实现完全不同,但它们在失败时:
• 明确抛出异常
• 使用可预期的异常类型
• 将失败原因清晰暴露给调用方
所以, 即便处于失败前进方向上, 它们依旧维持行为方面的一致性, 还能够一起纳入同一个呈现多种形态的接口之中。
(2)失败语义不一致的反例
result = read_all(BadSource())虽然在形式方面给出了 read() 方法, 然而在遭遇失败之际选取“沉默返回”, 既未对失败缘由予以说明, 又与既有的失败语义约定不相契合。
针对调用方来讲, 在这个时候没办法分辨, 返回的结果究竟是不是真正为空, 或者是在读取的进程之中出现了差错。
这种失败方式破坏了接口的语义一致性,使对象失去可替换性。
在那有着多态体系的情境里头, 达成成功之时所需遵从的路径得保证语义是一致的, 而当处于失败状况下的路径同样得确保语义是一致的。
失败方式的不一致,本质上等同于接口不稳定。
只有致使多态在真实系统得以实现长期成立, 才会出现对象于失败之际给出可预测行为, 且给出可理解行为, 并且给出可处理行为。
7.4 EAFP 与 LBYL 的设计哲学
社区常讨论两种设计立场:
value = default明显呈现出对EAFP(即先尝试再对付失败情况之前实施其他行动)有着相对LBYL(查看之后再进行跳跃式行动, 也就是先检查再着手开展行动)更为明显的倾向, 而其背后的本质根源在于:
• 失败在 中是合法行为
• 异常是结构化的失败表达
倘若失败呈现出混乱无序、难以预测的态势, 那么“先着手尝试, 而后应对失败”这般行径只会招致风险。正是鉴于已然把失败视作合法的行为举动, 并且借助异常予以标准化的表达呈现, 所以 EAFP 才演变成一种可靠的设计哲学。
因而, EAFP可不是那种所谓的“冒险写法”, 它而是基于失败多态的基础上面的一种富有理性内涵的选择。
7.5 明确失败条件的接口设计
成熟的 接口,应在设计阶段显式声明失败条件。
print(f"处理成功: {result}").() 的示例呈现出了一个关键的思想, 成熟的接口, 它不但要表明成功的时候做些什么, 而且更要表明失败的时候会出现什么情况。
通过文档和异常类型,接口明确回答了以下问题:
• 哪些失败是可能的
• 每种失败意味着什么
• 调用方应如何区分与处理
显式将失败条件纳入接口语义之中后, 这有着这样一种情形: 不同的实现能够于相同的失败约定状况下进行自由替换, 并且不会对调用方的逻辑造成破坏, 这是一种客观存在的状况, 你说。
于是乎, 那多态, 就不再仅仅局限于“成功路径之上的能够被替换”这般状况了, 还进一步拓展成为存在于全行为路径之上的具备可替换性质的情形。
7.6 失败多态的实际应用
失败多态的价值,并不止于“能被捕获”,还在于能被统一治理。
fetch_data(CacheSource())不在意具体的数据源是何种类型啦, 也不在意失败的内在缘由是什么, 它仅仅依靠一个事实哟: 这些对象在出现失败状况的时候, 会以事先约定好的异常形式将失败展现出来。
恰恰是由于失败路径具备一致语义此缘由, 横切逻辑也就包含重试、回退、熔断、降级这些方面, 才能够被抽取出来, 以独立于具体实现的状态而存在。
这正是失败多态在工程层面的现实意义。
小结
其中, 多态并非只是调用能够成功情况下具备的可替换特性, 而是有着失败路径方面, 另外还可被预期这样的特性。属于特别的异常类机制, 会把失败以结构化的方式呈现出来, 此便让不同的对象, 不管在成功层面, 抑或是处于失败的情况时, 都得以去依从一致的语义,进而切实达成行为所具备的可替换这一特性。