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()); ... }