`
xitongyunwei
  • 浏览: 925888 次
文章分类
社区版块
存档分类
最新评论

《代码整洁之道》读书笔记

 
阅读更多

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



分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics