Loading

好代码,坏代码

代码的特征
  • 健壮的
  • 可理解的
  • 适应需求的
  • 将修改点约束在局部
代码质量的定义
  • 四个高层目标

    1. 代码应该能正常工作

    2. 代码应该持续正常工作

      • 代码可能依赖于其他代码,而这些代码会被修改,更新和更换。

      • 任何新功能需求都意味着对代码进行修改。

      • 我们试图解决的问题也可能随时间的推移而发展:消费者的偏好、业务需求和技术考虑都可能变化。

    3. 代码应该适应不断变化的需求

    4. 代码不应该重复别人做过的工作

    总结来说核心就是减少对外部的依赖以及提高代码的复用性。

  • 代码质量的六个支柱

    1. 编写易于理解(可读)的代码。
    2. 避免意外
    3. 编写难以误用的代码
    4. 编写模块化代码
    5. 编写可重用,可推广的代码
    6. 编写可测试的代码并适当测试
抽象层次

代码结构是代码质量的根本特征之一,好的结构往往能建立清晰的抽象层次。

层次过厚引发的问题通常比层次过薄引发的问题更严重。如果我们不能确定,最好的办法往往是偏向于薄的层次。

  • 代码的层次

    函数

    好的函数应该想一个短句,如果描述一个函数需要多个语句,或者语句很复杂,那么函数的责任就过重了。

    类的最重要特征: 内聚性

    顺序内聚

    功能内聚

    关注点分离

    接口

    定义接口,确定该层次将要暴露的公共函数,是有时用于明确各个层次,以确保实现不会泄露的一种方法。

    包,命名空间,或者模块

代码契约

编写代码时考虑如下3件事是有用的

  • 对你来说显而易见,但是对其他人并不清晰的事情。
  • 其他工程师无意间视图破坏你的代码。
  • 过段时间你会忘记自己代码的相关情况。

如果是代码使用要求,不要依靠文档说明,用代码校验它。

代码契约的核心

  • 函数名称,或者类的名称,输入参数,输出参数。代码行为预期。
  • 前置条件,后置条件,不变量。
  • 使用检查与断言强制约束代码的使用条件。
如何处理软件错误
  • 系统可恢复错误与不可恢复错误的区别
  • 快速失败与大声失败的区别
  • 报告错误的不同技术与选用时的考虑因素

如果代码错误无法恢复,则代码中唯一明智的做法就是尝试限制破坏的程度,最大限制的增大工程师注意到的问题并修复的概率。

实际上,代码的编写者是无法确定代码错误是否为可恢复错误的,因为不知道被调用的场景。因此,合理的方法就是报告错误,并上上层代码自己决定是否处理错误。

代码错误时常见的处理方法:

  • 失败,由上层代码处理或者让系统崩溃。
  • 试图处理错误,并继续执行。

如果代码无法恢复,则在失败时应该立刻报告。记录错误信息。

使用“捕捉各种类型错误并记录而不是将其报告给更高层次的程序”的技术时应该极其谨慎,因为这对调用者来说是非常不好的,调用者根本无法尝试处理错误。

处理错误不好的方法:

  • 返回默认值
  • 空对象模式
  • 抑制错误,实际上什么也不做,但是调用者却错误的认为代码已经执行。

错误的报告方式:

  • 显示报告
  • 隐式报告

无法恢复的错误应该使用隐式错误处理方法。

编写易于理解的代码
  • 使用使代码不言自明的技术。
  • 确保代码中的细节对他人来说是清晰的。
  • 以正当的理由使用语言特性。

使用描述性的名称

  • 变量,函数和类的含义不言自明
  • 即使单独查看,各段代码也清晰。

适当使用注释

  • 解释代码完成的是什么。
  • 解释代码为什么完成这些工作。
  • 提供其他信息。如使用指南。

坚持一致的代码风格。

避免深层次的嵌套。

使函数的调用易于理解

  • 使用命名参数
  • 使用描述性参数类型
  • 避免使用特殊的常数值,可以定义为常量或者函数返回值。
如何避免代码是意料之外的
  • 避免返回魔法值。 调用者应该确却知道函数会返回什么,如果特殊情况返回一个完全没考虑到的值,代码就很容易有问题
空对象模式是无法取得某个值时返回空值(或空的可选值)的替代方法。这种方法的思路是,不返回空值,而是返回一个导致下游逻辑以无害方式运行的有效值。最简单的形式是返回一个空字符串或一个空列表,更复杂的形式则涉及实现一整个类,其中每个成员函数要么不做任何操作,要么返回一个默认值。
  • 是否返回空值或者默认值取决于在当前系统中是否合适。
  • 避免意料之外的副作用
    1. 向用户展示输出
    2. 将某些文件保存到文件或者数据库中
    3. 调用另一个系统,产生一些网络流量
    4. 更新缓存或使之失效。
  • 当多个函数需要处理输入参数时,不要在中间某个环节去修改输入参数。否则就会造成混乱,因为你不知道是谁
  • 避免编写误导性代码
编写难以误用的代码

接口设计原则

API和接口应该“易于使用、难以误用”

当一些假设不直观或者模糊不清,又没能阻止其他工程师做错事时,代码往往很容易被误用。代码被误用的一些常见方式如下:

  • 调用者提供无效的输入
  • 其他代码的副作用(如修改输入类)
  • 调用者没有在正确的时机或者以正确的顺序调用函数
  • 修改相关代码时破坏了某个假设

考虑使用不可变值

实现深度不可复制性

  • 防御性复制
  • 使用第三方库的不可变数据结构

尽可能使用专有类型,因为通用的数据类型,意味着什么参数都接收。更可能被误用。

提高代码的模块性

模块化的主旨之一是我们应创建可以轻松调整和重新配置的代码,而无须准确地知道如何调整或重新配置这些代码。实现这一点的关键目标之一是,不同功能(或需求)应该映射到代码库的不同部分。如果我们实现了这一点,随后有一项软件需求更改,我们应该只需要明显更改代码库中与该需求或功能相关的单一位置。

  • 考虑使用依赖注入
  • 倾向依赖接口

继承可能造成的问题

  • 无论你是否需要,继承的关键特征之一是,子类将继承超类提供的所有功能。
  • 继承性可能妨碍清晰的抽象层次
  • 继承性使代码难以适应不同情况

类应该只关注自身也就是所谓的最少知道原则

  • 防止在返回类型中泄露细节
  • 防止在异常中泄露细节
提高代码的可复用性

即使某个子问题的解决方案已经存在,也并不总意味着我们能够重用它。如果解决方案做出的假设不符合我们的用例,或者与我们不需要的其他逻辑绑定在一起,就会出现这种情况。

  • 注意各种假设

  • 如果假设是必须的则强制要求

  • 避免使用全局状态

  • 恰当的使用默认值

    将使用默认值的相关决策切换到调用者手中往往使代码更容易重用。但在不支持空值合并运算符的编程语言中返回空值,将迫使调用者编写重复的代码来处理它。

单元测试
  • 代码的可测试性,以及单元测试应该在一开始就得到考虑,而不是在代码编写完才去考虑。

  • 好的单元测试拥有的特征

    1. 准确检测到破坏

      如果代码遭到破坏,测试应该失败,而且,测试应该只在代码确实被破坏的时候失败。

    2. 与实现细节无关

      理想情况下,测试细节的改变不会改变测试代码的改变。

    3. 充分解释失败,如果代码被破坏应该得到清晰的信息。

    4. 测试代码容易理解

    5. 测试代码运行成本低

posted @ 2023-02-03 17:35  Test002  阅读(26)  评论(0)    收藏  举报