zoukankan      html  css  js  c++  java
  • 《代码整洁之道》读书笔记

    2005年,Elisabeth递给我一条绿色腕带,上面写着Test Obsessed沉迷测试的字样,我高兴地带上。我发现自己无法取下腕带,不仅是因为腕带很紧,而且那也是精神上的紧箍咒。那腕带就是我职业道德的宣告,也是我承诺尽己所能写出最好代码的提示。写代码时,我用余光瞟见它。它一直提醒我,我做了写出整洁代码的承诺。


    1 怎样才算整洁?

    1.1 花时间保持代码的整洁,不但有关效率,还有关生存。

    1.2 本该是病人说了算;但医生却绝对应该拒绝遵从。为什么?因为医生比病人更了解疾病。医生如果按病人说的办,就是一种不专业的态度。同理,程序员遵从不了解混乱风险的经理的意愿,也是不专业的做法。

    1.3 写整洁代码需要遵循大量的小技巧,贯彻刻苦习得的整洁感。这种代码感就是关键所在。有些人生而有之。有些人费点劲才能得到。


    2 变量

    2.1 变量、常量、方法、类的命名,一旦发现更好的名称,就换掉旧的。

    2.2 变量名称的长短应与其作用域大小相对应。单字母名称仅用于短方法中的本地变量,在代码多处使用的变量或常量,应赋予便于搜索的名称。

    2.3 关于名称的编码:Fortran语言要求首字母体现出类型,导致了编码的产生。BASIC早期版本只允许使用一个字母加上一位数字。匈牙利语标记法将这种态势愈演愈烈。那时候编译器并不做类型检查,程序员需要匈牙利语标记法来帮助自己记住类型。现代编程语言具有更丰富的类型系统,编译机也记得并强制使用类型。Java程序员不需要类型编码,对象都是强类型。


    3 方法

    3.1 函数的第一规则是要短小,第二规则还要更短小。函数应该做一件事。做好这件事。只做这一件事。

    3.2 我看惯了Swing程序中长度数以里计的函数。但这个程序中每个函数都只有两行、三行或者四行。每个函数都一目了然。每个函数都只说一件事。而且,每个函数都依序把你带到下一个函数。这就是函数应该达到的短小程度!

    3.3 问题在于很难知道那件该做的事是什么。如果函数只是做了该函数名下同一抽象层上的步骤,则该函数还是只做了一件事。

    3.4 大师级程序员把系统当做故事来讲,而不是当做程序来写。


    4 注释

    4.1 注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败。注释是一种失败。如果你发现自己需要写注释,再想想看是否有办法翻盘,用代码来表达。每次用代码表达,你都该夸奖一下自己。


    5 对象和数据结构

    5.1 我们不想其他人依赖这些私有变量。我们还想在心血来潮时能自由修改其类型或实现。那么为什么还有那么多程序员给对象自动添加赋值器和取值器,将私有变量公之于众,如同它们根本就是公共变量一般呢?

    5.2 要以最好的方式呈现某个对象包含的数据,需要做严肃的思考。傻乐着乱加赋值器和取值器是最坏的选择。

    5.3 对象把数据隐藏于抽象之后,暴露操作数据的函数。数据结构暴露其数据,不提供有意义的函数。

    5.4 对象与数据结构之间的二分原理:过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数。面向对象代码便于在不改动既有函数的前提下添加新类。

    5.5 得墨忒耳率:对象不该通过存取器暴露其内部结构。以下代码被称为火车失事。

    final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
    


    6 异常处理

    6.1 在很久以前,许多语言都不支持异常。你要么设置一个错误标识,要么返回给调用者检查的错误码。

    public void sendShutDown() {
            DeviceHandle handle = getHandle(DEV1);
            if (handle != DeviceHandle.INVALID) {
                    retrieveDeviceRecord(handle);
                    if (record.getStatus() != DEVICE_SUSPENDED) {
                            pauseDevice(handle);
                            clearDeviceWorkQueue(handle);
                            closeDevice(handle);
                    } else {
                            logger.log("Device suspended. Unable to shut down");
                    }
            } else {
                    logger.log("Invalid handle for : " + DEV1.toString());
            }
    }
    

    6.2 Try/catch代码块丑陋不堪。它们搞乱了代码结构,把错误处理与正常流程混为一谈。最好把try和catch代码块的主体部分抽离出来,另外形成函数。

    public void sendShutDown() {
            try {
                    tryToShutDown();
            } catch (DeviceShutDownError e) {
                    logger.log(e)
            }
    }
    
    private void tryToShutDown() {
            DeviceHandle handle = getHandle(DEV1);
            DeviceRecord record = retrieveDeviceRecord(handle);
            pauseDevice(handle);
            clearDeviceWorkQueue(handle);
            closeDevice(handle);
    }
    
    private DeviceHandle getHandle(DeviceID id) {
            ...
            throw new DeviceShutDownError("Invalid handle for:" + id.toString());
            ...
    }
    

    6.3 使用不可控异常。可控异常的代价是违反开闭原则。如果你在方法中抛出了可控异常,而catch语句在三个层级之上,你就得在catch语句和抛出异常处之间的每个方法签名中声明该异常。

    6.4 遵循前面的建议,在业务逻辑和错误处理代码之间就会有良好的区隔。大量代码会开始变得像是整洁而简朴的算法。不过有时你也许不愿这样做,把错误检测推到了程序的边缘地带。有种手法叫特例模式,创建一个类用来处理特例。


    7 边界

    7.1 第三方程序包和框架提供者追求普适性,这样就能在多个环境中工作,吸引广泛的用户。而使用者想要满足特定需求的接口。这种张力会导致系统在边界上出现问题。

    7.2 不要在生产代码中试验新东西,而是编写测试来浏览和理解第三方代码。Jim Newkirk把这叫做学习型测试。


    8 单元测试

    8.1 测试必须随生产代码的演进而修改。测试越脏就越难修改。测试代码越纠结,你就越可能花更多时间塞进新测试。修改生产代码后,旧测试就会开始失败。

    8.2 无论架构多有扩展性,无论设计划分得有多好,没有了测试,你就很难改动,因为你担忧改动会引入不可预知的缺陷。

    8.3 测试应该够快。如果测试运行缓慢,你就不会想要频繁地运行它。

    8.4 你应该可以单独运行每个测试,以及以任何顺序运行测试。

    8.5 测试应当可以在任何环境中重复通过。你应该能够在生产环境、质检环境中运行测试,也能够在无网络的列车上用笔记本电脑运行测试。如果测试不能在任意环境中重复,你就总会有个解释其失败的借口。

    8.6 无论通过还是失败,你都不应该查看日志文件来确认测试是否通过。如果测试不能自足验证,运行测试就需要更长的手工操作时间。


    9 类

    9.1 通常而言,方法操作的变量越多,就越黏聚到类上。如果一个类中的每个变量都被每个方法所使用,则该类具有最大的内聚性。内聚性高,意味着类中的方法和变量相互依赖,互相结合成一个逻辑整体。

    9.2 你想要拆出来的代码使用了该函数中声明的4个变量,是否必须将这4个变量作为参数传递到新函数中呢?没必要!只要将4个变量提升为类的实体变量,完全无需传递任何变量。可惜这也意味着类丧失了内聚性,因为堆积了越来越多只为少量函数共享而存在的实体变量。等一下!如果有些函数想要共享某些变量,为什么不让它们拥有自己的类呢?


    10 系统

    10.1 将全部构造过程搬迁至main或者被称为main的模块中,设计系统的其余部分时,假设所有对象都已正确设置。

    10.2 控制反转将第二权责从对象中拿出来,转移到另一个专注于此的对象中,从而遵循了单一权责原则。


    11 并发编程

    11.1 Web应用的Servlet标准模式。这类系统运行于Web或EJB容器的保护伞下,容器为你部分地处理并发问题。当有Web请求时,servlet就会异步执行。程序员无需管理所有的请求。实际上,Web容器提供的解耦手段离完美还差得远。



  • 相关阅读:
    基于element-ui图片封装组件
    计算时间间隔具体每一天
    C语言学习笔记 —— 函数作为参数
    AtCoder Beginner Contest 049 题解
    AtCoder Beginner Contest 048 题解
    AtCoder Beginner Contest 047 题解
    AtCoder Beginner Contest 046 题解
    AtCoder Beginner Contest 045 题解
    AtCoder Beginner Contest 044 题解
    AtCoder Beginner Contest 043 题解
  • 原文地址:https://www.cnblogs.com/xiaomaohai/p/6157755.html
Copyright © 2011-2022 走看看