美文网首页
Clean Code

Clean Code

作者: my_passion | 来源:发表于2021-12-08 22:50 被阅读0次

禁止转载

<< Clean Code >>

概述
    1. 本书 观念
        代码质量 与其 整洁度 成正比
    
    2. 好/坏细节
        神在细节之处。细节中自有天地
        不好的细节 会将 大局的魅力尽毁

    3. 5S: 本质是 精益(Lean)
        整理
            sort: 物皆有其位
                用 恰当 命令 等手段, 找到其位置
            
        整顿
            systematize: 物尽归其位
            
            
        锃(zeng)亮
            清除 无用的东西
        
        标准化
            保持一贯的代码风格和实践手段
                简单地保持一致缩进风格就能创造价值
            
        自律
            贯彻规程
    
Chapter1 整洁代码

    1. 代码永存
        因为需求往往是 模糊的, 机器无法透彻地理解人们
        
    2. 糟糕代码 可能 毁掉公司
        Latar equals never ( 稍后等于永不 )
        
    3. 混乱的代价
                华丽新设计: 未必就好
              / 花样  
             /
    越来越乱 —— 程序员应有的态度: 准确地告知 经理 代码中的混乱, 并争取到改进的时间预算
             \
              \ 
                代码写的快的唯一方法: 始终尽可能保持代码整洁
                
    4. 什么是 整洁代码

    C++ 之父
        优雅 高效
        代码逻辑 直截了当   -> 叫 缺陷难以隐藏
        依赖   减少                 -> 便于维护
        性能   调至最优       -> 省得 引诱 别人 乱优化, 搞出一堆混乱来
        错误处理 分层战略
        只做好一件事

    Ron
    简单代码
        能通过所有测试
        没重复
        体现系统全部设计理念
        包含尽量少的 实体 —— 类 函数 等手段
        
        1) 代码重复 -> 去重
        
        2) 有意义的命名
        
        3) 对象 / 方法 职责太多 -> 切分 / 抽取 (Extract Method)
        
        4) 程序由 极为相似的元素构成
            在集合中查找某物 -> 封装到 更抽象的 类/方法
        
    5. 使代码易度 实际也 使之易写

    6. 修改代码 之后要比之前 干净   
Chapter2 有意义的命名

                        良好的描述技巧
                      / 
    取好名字 的 难处 
                      \
                        共有文化背景
    1. 名副其实
        以 功能命名
        有意义 胜于 模糊
        
    2. 避免误导
        别用 accountList 表示一组账户, 除非 它真是 `List 类型`

    3. 有意义的区分
        1) 数字: 通常没有意义
        
        2) 废话都是冗余
            Variable/Table 不该出现在 变量/表 名 中

    4. 名称 应该 能读出来
        尽量 用完整单词组合, 除非 单词的缩写 能读出来
        modificationTimestamp
        recordId
        
    5. 名称 应该 易搜索
        不要太短 -> 搜索 匹配太多
        
        名称长短 应与其 作用域大小 对应
        
    6. 避免编码
        1) 避免把 类型/作用域 编进名称
        
        2) 成员前缀 m_ -> 多余
            读代码时, 应学会 无视 前/后缀, 只看到 name 中 有意义的部分
            
        3) 接口与实现
            让 其中之一 带 前/后 缀 是个好主意
                ShapeFactory (接口类名) / ShapeFactoryImp (实现类名)
            
            
    7. 类名 应该是 名词
        Customer AddressParser

    8. 方法名 应该是 动词
        save get/set/is

    9. 每个概念 对应 1个词
        给每个 抽象概念 选1个词, 并 一以贯之
            3 个 类 中 都表示 获取的方法 分别叫 fetch/retrieve/get -> 3 个类中 用同一个词 才好, 如 get
            
    10. 别用双关语
        要遵循 一词一义
            多个类 均有 add 方法, 只要其 para list + return value 语义等价就好
            
            但 将 elem 放到 collection 中, add 显然不合适, 应该用 append/insert
        
                解决方案领域          
             /
    11. 分离 
             \
                问题领域 
            
            
    12. 添加 有意义的 语境
                                给 name 加 前/后缀
                              /
        很少有名称能 自我说明 
                              \
                                用 良好命名的 类/函数/namespace 放 name, 给读者提供 语境

                                addrFirstName addrStreet addrCity 
                              /    |
        firstName street city      |
                              \    | 
                                Address
                                    - firstName
                                    - street
                                    - city
        
Chapter3 函数

    语言 用来描述系统。函数 是 语言的 动词, 类 是 名词

    大师级程序员 `把系统当作 故事 来讲`, 而不是当作程序来写
     
    1. 短小
        20 行封顶最好
        
    2. 只做一件事
        函数应该 做 一件事。做好 这件事。只做 这一件事
        
        怎样算只做一件事?
            3种描述
                函数 只做了 该函数名下 `同一抽象层 上的步骤`
            
                该函数 不能再拆出一个函数, 而拆出的函数 不仅仅是 单纯地 重新诠释其实现
            
                函数无法被合理地切分为 多个区段
        
    3. 每个函数 一个抽象层

    自顶向下 读代码: 向下规则

        让 每个函数 后面都跟着 下一抽象层的函数
            => 查 func list 时, 就能循抽象层向下阅读了
        

        程序就像一系列 `TO 起头的段落`, 每一段都 `描述 当前抽象层`,
            并 `引用 下一抽象层` 的后续 `TO 起头段落`
            
            // eg
            要 容纳 设置和分拆 步骤, 就先 `容纳设置步骤`, 然后 纳入测试页面内容, 再 纳入分拆步骤
                要 `容纳设置步骤`, 如果是 套件, 就 纳入 `套件设置步骤`, 然后 ...
                    要 纳入 `套件设置步骤`, 先..., 再 ...

        
        String func()
        {
            
            buffer.append("\n"); // 低 抽象层
            
            String pagePathName = PathParser.render(pagePath); // 中 抽象层
            
            return pageData.getHtml(); // 高 抽象层
        }   
        
        class SetupTeardownIncluder
        {
        private:
            PageData pageData;
            bool isSuite;
            //...
        publid:
            static String render(PageData pageData)
            {
                return render(pageData, false);
            }
            
            static String render(PageData pageData, bool isSuite)
            {
                return new SetupTeardownIncluder(pageData).render(isSuite);
            }
        private:
            SetupTeardownIncluder(PageData pageData)
            {
                this->pageData = pageData;
                // ...
            }
            
            String render(bool isSuite)
            {
                this.isSuite = isSuite;
                if( isTestPage() )
                    includeSetupAndTeardownPages();
                return pageData.getHtml();
            }
            
            void includeSetupAndTeardownPages()
            {
                includeSetupPages();
                includePageContent();
                includeTeardownPages();
                updatePageContent();
            }
            
            void includeSetupPages() 
            {
                if(isSuite)
                    includeSuiteSetupPage(); // `引用 下一抽象层` 的后续 `TO 起头段落`
                includeSetupPage();
            }
        }
        
        
    4. switch 语句

        switch 语句 天生要做 N 件事 => 违反 单一职责 + 开放封闭 原则
            |
            |  可部分避开 switch, 最多只在 `创建多态对象` 时出现
            |
            |  (1) switch 语句 -> 埋到 Factory Method 底下, 不让 client 看到
            |           Factory Method 用 switch `创建多态对象`
            |
            |  (2) 原来使用 switch 的 func -> 由 该类 的 vf 多态 地 派遣到 Derived 类 去具体实现
            |/
        Factory Method + 多态 

        例子见 << 重构 >> 中 Replace Type Code with SubClass
     
    5. 使用 描述性 (函数)名称 
        长而具有描述性的名称 比 短而令人费解的名称好, 比 描述性长注释好
     
    6. 函数参数
        参数 与 函数名 处于 不同的抽象层
        参数应越少越好
        测试 覆盖所有可能值 的组合 让人生畏

        1) 一元函数
            
        2) 二元函数
            2 个参数 是 单个参数的 有序组成部分
        
        3) 三元参数
            忽略不必要的
        
        4) 标识参数/布尔值参数 -> 消除
        
        5) 参数对象
        
    7. 无 副作用
        (1) check...() 中 却 出现 Session.initialize() 
            -> check...() 有副作用 
            -> 若不看内部, 以为只有 check 动作 
            -> check...AndInitializeSession() 更好
        
        (2) 输出参数 在 OO 中 需求基本消失, 因为 this 也有 输出的意味在内
            public void appendFooter(StringBuffer report)
                |
                |  最好换成 OO 调用形式
                |/
            report.appendFooter();
            
    8. 分隔 set/get

    9. 用 异常 代替 返回错误码
Chapter4 注释

            好注释: 意义重大
         /
    1.  /            
        \            乱七八糟 的 注释 -> 搞乱代码
         \         /
            坏注释
                   \
                     陈旧、提供错误信息的注释 -> 破坏性
                        注释 可能 与代码 分隔 -> 越来越不准确
                
    2. 注释 不能美化 糟糕的代码 -> 写注释 不如 重构代码

    3. 注释 很多简单到 用一个 描述函数 替换


                    1) 法律信息
                 /  
                |—— 2) 对某个决定背后意图的解释
    4. 好注释  |
                |—— 3) 阐明 晦涩
                
                |—— 4) Warning
                 \  
                    5) TODO
                        程序员认为应该做, 但由于某些原因还没做
        
        其余基本都是坏注释
        
Chapter5 格式

    1. 格式的目的
        好的 `代码风格 和 可读性` 利于代码 `可维护性 和 扩展性`
        
        即使 代码不复存在, 你的 `风格 和 律条` 仍存活
        
                    1) 向报纸学习
                 /               隔开 概念: 函数间 等
                |              / 
                              / 用
                |—— 2) 空白行 
                              \ 不用
                |              \ 
                                 靠近的代码: 紧密关联
    2. 垂直格式 |   

                |—— 3) 变量声明: 尽可能 靠近其 使用位置
                 \  
                    4) 相关(调用关系) 函数: 放到一起, caller 放 上面
                    
                              隔开 弱关联事物
                            /       
                           / 用      
                    1) 空格  
                 /         \ 不用 
                |           \
                              连接 紧密相关的事物
    3. 横向格式 |

                |—— 2) 水平对齐
                            Socket      socket;
                |           InputStream input;
                 \              
                    3) 缩进
                    
    4. 1个团队 1套 代码格式管理规则, 然后 贯彻之
Chapter6 对象 和 数据结构

    mem 设为 private: 不想让其他人 依赖 它
    
    1. 数据抽象

        1) 隐藏实现 并非只是在 `变量之上 放一个 函数层` 那么简单
           隐藏实现 关乎抽象!
           类 的意义: 曝露 抽象接口, 使 Client 无需了解 `数据结构 的实现` 就能操作 `数据本体`

        2) 我们不愿 `曝露 数据细节`, 更愿意 `以 抽象形态 表述数据`
           要以 最好的方式 呈现 对象包含的数据
        
        
        (3) eg
        
                            1) 你不知道 `相应的 实现` 是在 笛卡尔坐标系 还是 级坐标系, 还是 其他。
                          /     但 `该 接口` 却 呈现了一种 `数据结构 ( 即 Point )`
        抽象 Point 的漂亮       
                          \ 
                            2) 接口 固定了一套 `存取策略`: 
                                可 `单独读取` 某坐标, 但必须通过 `一次原子操作` 设 所有坐标
        
        
        
        
        // 具象 Point
        class Point
        {
        public:
            double x;
            double y;
        };
         
        // 抽象 Point
        interface Point
        {
        public:
            double getX();
            double getY();
            void setCartesian(double x, double y); // Cartesian 笛卡尔坐标
            
            double getR();
            double getTheta();
            void setPolar(double r, double theta); // 极坐标
        };
        
    2. 数据结构、对象 的 反对称性

                
    数据结构 与 对象 本质 对立

                    曝露 数据
                 /
        数据结构
                 \
                    没有提供 有意义的 函数
        
                    隐藏 数据 于 抽象 之后 
                /       
        对象
                \
                    曝露 操作数据的 函数
                    
        =>      
        —————————————————————————————————————————————————————————————————————————————————   
                                    |   便于 
        —————————————————————————————————————————————————————————————————————————————————                               
        过程式 (使用 数据结构) 代码   |   在 不改动 `既有 数据结构` 的前提下 添加 `新 函数`  

        OO                     代码 |   在 不改动 `既有 函数`     的前提下 添加 `新 类`
        —————————————————————————————————————————————————————————————————————————————————
                |
                | 反过来说
                |

        —————————————————————————————————————————————————————————————————————————————————
                                    |   难以 
        —————————————————————————————————————————————————————————————————————————————————                               
        过程式 (使用 数据结构) 代码   |   添加 `新 数据结构`, 因为必须修改 `所有 函数`

        OO                     代码 |   添加 `新 函数`,     因为必须修改 `所有 类`
        —————————————————————————————————————————————————————————————————————————————————
        
    => 
        对 OO 较难的事, 对 过程式 却较容易, 反之亦然
        
                        新数据 类型 时 -> 对象 和 OO 更合适
                     /
        系统 需要添
                     \
                        新 函数     时 -> 过程式 更合适
                        
        一切都是对象 只是1个传说
        
        
        eg
        
        // 过程式
        // 1) 给 Geometry 加 primeter() 函数, 各 形状类 不变
        // 2) 加 一个 新形状类 -> 改 Geometry 中 所有函数
        class Square
        {
        public:
            Point topLeft;
            double side;
        };
        
        class Circle
        {
        public:
            Point center;
            public double radius;
        };
        
        class Geometry
        {
        public:
            double PI = 3.14;
            
            double area(Object shape)
            {
                if( typeof(shape) == Square )
                {
                    Square s = (Square)shape;
                    return s.side * s.side;
                }
                else if( typeof(shape) == Circle )
                {
                    Circle c = (Circle)shape;
                    return PI * c.side * c.side;
                }
            }
        };
        
        // 多态式
        class Square: public Shape
        {
        private:
            Point topLeft;
            double side;
        public:
            double area()
            {
                return side * side();
            }
        };
        
        class Circle: public Shape
        {
        private:
            Point center;
            double radius;
        public:
            double PI = 3.14;
            
            double area()
            {
                return PI * radius * radius;
            }
        };
        
    3. Demeter 定律

        模块 不应了解它所操作的 对象 的 内部情形
        
        对象 隐藏数据, 曝露 操作
        
        类 A 的 方法 f 只应该调用 以下对象 的 方法
            1) A
            2) f 创建的对象
            3) 作 参数 传给 f 的 对象
            4) A 的 实体变量 持有的 对象
        
        方法 `不应调用` 由任何函数 `返回的对象 的 方法` 
            即 只跟朋友谈话, 不跟陌生人谈话
            
        违反 Demeter 定律的例子
        
        // ScratchDir: 暂存目录
        String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
        
    (1) 连串调用 时 坏风格, 应 切分
        Options opts = ctxt.getOptions();
        File scratchDir = opts.getScratchDir(); 
        String outputDir = scratchDir.getAbsolutePath();
        
        上述代码 是否违反 Demeter 定律?
            
        若 ctxt / Options / ScratchDir  是 对象/数据结构 -> 违反 / 不违反
        
    (2) 混杂
            无论初衷是什么, public set()/get() 函数 都把 private memData 公开化,
            诱导 外部函数 以 过程式程序 使用 数据结构的方式 使用 这些 memData
            
    (3) 隐藏结构

        ctxt.getAbsolutePathOfScratchDirOption();
        
        或
        
        ctxt.getgetScratchDirOption().getAbsolutePath(); // 假定 getgetScratchDirOption() 返回的是 数据结构 而非 对象
        
        感觉都不太好
        
        如果 ctxt 是个 `对象`, 就应该 要求它 `做点什么`, 不该要求它 给出内部情形
        
        哪为何要 得到 临时目录的绝对路径 ? 看同模块中 其 用途即可
        
        String outFile = outputDir + "/" + className.replace('.', '/') + ".class";
        FileOutputStream fout = new FileOutputStream(outFile);
        BufferedOutputStream bos = new BufferedOutputStream(fout);
        
        不同层级的细节混杂
        初衷: 为了 创建指定名称的临时文件流
        => 直接让 ctxt 做这件事
        
        BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);
        
        看起来像 对象做的事了 ! ctxt 隐藏了其 内部结构, 防止 当前函数 因 浏览 它不该知道的对象 而 违法 Demeter 定律
        
    4. 数据传送对象 DTO
        (1) DTO 
            只有 public 变量 + 没有 函数 -> 最精炼的 数据结构
            
            应用
                与 数据库 通信、解析 套接字传递的消息

        (2) Active Record: 特殊的 DTO
            public(可 bean /豆式 访问 的) 变量 + save() / finc() 等 函数
            
            不应把 业务规则方法 放进 Active Record
            -> 解决
                    把 Active Record 当作 数据结构, 创建 包含 业务规则、隐藏 内部实际 (可能就是 Active Record 实体 ) 的独立对象

        (3) "bean" 结构
                private 变量 + set()/get() 函数
Chapter7 错误处理

    错误发生时, 确保代码照常工作
    
    1. 使用 异常 而非 返回错误码
        好处
            1) 代码更整洁
            2) 隔离 业务处理流程 和 错误处理

        public class DeviceController
        {
            public void sendShutDown()
            {
                DeviceHandle handle = getHandle(DEV1);
                
                if(handle != DeviceHandle.INVALID)
                {
                    DeviceRecord record = getDeviceRecord(handle);
                    
                    if(record.getStatus() != DEVICE_SUSPEND)
                    {
                        pauseDevice(handle);
                        ....
                    }
                    else
                    {
                        logger.log("Device suspend.")
                    }
                }
                else
                {
                    logger.log("Invalid handle for: " + DEV1.toString() );
                }
            }
        }
            |
            | 依 caller 需要 定义 异常类 DeviceShutDownError
            |/

        public class DeviceController
        {
            public void sendShutDown()
            {
                try
                {
                    tryToShutDown();
                }
                catch(DeviceShutDownError e)
                {
                    logger.log(e);
                }
            }

            private void tryToShutDown() throws DeviceShutDownError
            {
                DeviceHandle handle = getHandle(DEV1);
                
                DeviceRecord record = getDeviceRecord(handle);
                
                pauseDevice(handle);
                ...
            }
            
            private DeviceHandle getHandle(DeviceID id)
            {
                ...
                
                throw new DeviceShutDownError("Invalid handle for: " + id.toString() );
            }
                
        }

    2. 先写 Try-Catch-Finally
        异常的妙处: 定义了一个范围
        
        try/catch 代码块 像事物/将程序维持在一种持续状态
        
    3. 别返回 null 值

    4. 别传递 null 值
Chapter8 边界

    将 外来代码 整合进 自己的代码
    
    1. 使用 第三方代码

        第三方代码: 普适性
                            \
                               => 系统边界 出问题
                            /
        使用者    : 特定需求
            
            
        包容 Sensor 类 的 Map 映射图

        Map sensors = new HashMap();
        Sensor s = (Sensor)sensors.get(sensorId);
            |
            |   泛型
            |/
        Map<Sensor> sensors = new HashMap();
        ...
        Sensor s = sensors.get(sensorId);
            |
            |       Map 接口修改时, 很多地方要改
            |
            |/
        public class Sensors
        {
            private Map sensors = new HashMap();
            
            public Sensor getById(string id)
            {
                return (Sensor)sensors.get(id);
            }
        }


    2. 浏览和学习边界

    学习性测试
        编写测试 来 浏览 和 理解 第三方代码
        
    3. 学习 log4j

        文档 无需看太久 -> 编写 测试用例 -> 运行 -> 出错 -> 再多读文档 / Google / 百度查解决方案
                                |\                                      |
                                |                                       |
                                |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|

        封装自己的 日志类
        
        将 程序 业务处理部分 与 日志类 接口隔离
        
    4. 使用 尚不存在的代码

        定义自己的使用的 接口(在我们控制下, 有助于保持 Client 代码 更可读 ) + Adapter 模式 + FakeImplement
        
     —————————————————————————        \  —————————————————————————————
    | CommunicationsController| ——————— |  << interface >>            |
    |                         |       / |   Transmitter               |
     —————————————————————————          |—————————————————————————————| 
                                        |+ transmit(frequency, stream)|
                                         —————————————————————————————  
                                                    /_\
                                                     |
                                              _ _ _ _|_ _ _ _ _
                                             |                 |
                                             |                 |                               ——————————————
                                 ————————————————            ————————————————————           \ | << future >>
                                |FakeTransmitter |          | TransmitterAdapter |——————————— | Transmitter  |
                                |                |          |                    |          /  ——————————————
                                 ————————————————            ————————————————————             | API          |
                                                                                               ——————————————
                                                                                               
    5. 整洁边界

        代码中 少数几处引用 第三方 边界接口
Chapter9 单元测试
    
    测试驱动开发
    
    测试代码 和 生产代码 一样重要
    
    自动化单元测试程序
    
    整洁测试: 可读性好、明确、简洁
    
    每个测试 一个概念
    
    FIRST
        F fast
        I Independent
        R Repeatable
        S Self-Validating
        T Timely

Chapter10 类

    1. 类的组织
        变量列表
        
        static 常量
        
        private memData
        
        public func
            |
            |   调
            |/
        private 工具函数

    2. 类应该短小

    函数 大小: 以 行数 衡量

    类 大小  : 以 职责 衡量

    类名 应 描述其 职责

    无法为类命名为 精确的名称 时, 类 就太长了


    1) SRP: 单一职责 原则
        类 或 模块 应有且只有 一条加以修改的理由
        
    2) 内聚
        高内聚: 类中 变量和方法 相互依赖 合成一个逻辑整体
        
    3) 保持 高内聚 就会得到许多 短小的类

        大函数 拆分为 小函数: 原本 local var 要作 参数 传递 
        -> 若 这些 local var 提升为 类的 memData, 则 无需传递
        -> 类 的 内聚性 降低: 堆积 越来越多 只为 少量函数 共享 而存在的 memData
        -> 让 这些 函数 拥有自己的类: 拆分类 (按职责拆分)

    4) DIP 依赖倒置 原则
        类 应该 依赖于 抽象, 而不是 实现细节
        
        最佳实践
            抽象类 只呈现 概念
            具体类 包含 实现细节
            让 Client 类 依赖 抽象类/接口 -> 以 解耦 Client 与 Implement
    
Chapter11 系统

    1. 将系统的 构造 与 使用 分开
    软件系统 应将 启始过程  与 之后的运行时逻辑 分离开

    (1) 延迟 初始化/赋值
        真正用到对象前, 无需操心构造, 启动时间更短
        
        public Service getService()
        {
            if(service == null)
                service = new MyServiceImp(...); // 构造 与 运行时逻辑 混杂 -> 违反 单一职责
            return service;
        }

        仅出现1次的 延迟初始化 还不算严重问题, 但 应用程序中往往有许多 类似情况
        
    (2) 将 构造 分解到 main 模块

        main 模块 创建系统所需对象, 传递给 应用程序, 应用程序 只管使用
        
         ———————————————        2: run    \   ————————————————
        | main/构造过程 | —————————/———————  |  application  |
         ———————————————          /       /   ————————————————
                |                |                  |
                |                |                  |
                | 1: build       |                  | 
                |                |                  |
               \|/               |                 \|/                  
         ——————————————   1.1: construct  \  ————————————————————
        | Builder      |  ———————|————————— | Configured Object |
         ——————————————          |        /  ————————————————————
        
        
        依赖箭头 从 main 向外, 表示 应用程序 对 main/构造过程 一无所知
        
        
    (3) 工厂

    让 应用程序 负责 确定 何时创建对象


         ———————————————        2: run    \   ——————————————————————
        | main/构造过程 | —————————/———————  |  OrderProcessing     |
         ———————————————          /       /   ——————————————————————
                |                |                      |           \
                |                |                      |            \
                | 1: build       |                      |             \
                |                |                      |              \
               \|/               |                     \|/              \           
         —————————————————————   | 1.1: construct    ——————————————————  \
        | LineItemFactoiryImp | —|———————————————|\ | LineItemFactoiry |  \
         —————————————————————   |               |/ |———————————————————   \
                    |                               | + makeLineItem   |    \
                    |                                ———————————————————     \
                    |                                                         \/
                    |                                                     \  ————————————
                    |—————————————————————————————————————————————————————— |   LineItem |
                                                                          /  ————————————
            
    依赖都从 main 指向 OrderProcessing 应用程序, 表示 应用程序 与 如何构建 LineItem 的细节 分离

    构建能力 由 在 main 这一边的 LineItemFactoiryImp 持有

    但 应用程序能 完全控制 LineItem 何时构建

    (4) 依赖注入 控制反转

Chapter12 迭进

    1. Kent Beck 简单设计 4 原则
        (1) 运行所有测试
        
        (2) 消除重复
        
        (3) 保证表达力
        
        (4) 尽可能减少 类 和 方法的数量
            类和方法 短小 + 整个系统短小
            避免教条主义
                1) 为 每个类 创建 接口
                2) 字段 和 行为 必须切分到 数据类 和 行为类
            
        上述优先级 依次降低


相关文章

网友评论

      本文标题:Clean Code

      本文链接:https://www.haomeiwen.com/subject/zusxfrtx.html