"我可以列出我留意到的整洁代码的所有特点, 但其中有一条是根本性的, 整洁的代码总是看起来像是某位特别在意他的人写的. 几乎没有改进的余地, 代码作者设么都想到了, 如果你企图改进它, 总会回到原点, 赞叹某人留给你的代码" ---Michael Feathers
"整洁的代码只做好一件事" ---Bjarne Stroustrup
即顾名思义, 变量. 函数或者类的名称应该能告诉你, 它为什么会存在, 他做什么事, 应该怎么用, 名称应该尽量不需要注释补充亦能表示其含义.
类名和对象应该是名词或者名词短语, 方法名应该是动词或者动词短语.
- //不好的命名
- public boolean stateMachine(int smStatus) {
- //...
- }
- public boolean doAction() {
- //...
- }
- //好的命名
- public boolean moveStateMachineToStatus(int smStatus) {
- //...
- }
- public boolean doNextStepInProviderTechnique() {
- //...
- }
名字中不要有冗余, 定义一个 List 类型变量, xxxList 不如 xxx 的复数形式简洁, List 字眼就属于冗余字段.
使用常见的布尔含义的命名: 如 done;error;found;success;
可以添加前缀 is,has 来转换布尔, 但是 isSuccess 不如 success 可读性好
避免使用负向变量命名: 如 notFound, 设想 if(!notFound) 是不是觉得别扭. 负向条件比正向条件更加难于理解, 应该尽量避免.
这里是指一个函数的代码量, 不是指函数名称要短小. 函数应该是在做一件从语义上无法再次拆解的事情.
函数应该做一件事, 做好这一件事, 而且只做这一件事.
写多长的函数比较合适
约定: 不超过一屏幕, 长度并不是问题, 关键在于函数名称和函数体之前的语义距离, 函数应该很短, 也可以较长. 只要保证函数只做这一件事.
拆分函数的技巧
寻找注释, 如果函数中某部分代码前面有一行注释, 一般可以把这段代码替换成一个函数. 此外, 当感觉需要注释来说明一些什么的时候, 就要考虑把要说明的这些东西封装成一个独立的函数.
这一点需要多一些说明, 要确保函数只做一件事, 函数的语句要在同一个抽象层级上.
- public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- String fileId = request.getParameter("FILEID");
- if (fileId == null) {
- throw new ServletException("Invalid FileId"); }
- String partNum = request.getParameter("PART"); int part = 1;
- if (partNum != null) {
- part = Integer.valueOf(partNum).intValue(); }
- boolean isLast = "Y".equals(request.getParameter("LAST"));
- boolean getName="Y".equals(request.getParameter("GETNAME"));
- String fileName =
- MitoDownloadCache.INSTANCE.getFileName(fileId, part);
- if (fileName== null) {
- throw new ServletException("Invalid FileName"); }
- MitoConfig mitoCfg = new MitoConfig();
- File file = new File(mitoCfg.getPrintClientDataPath()+"/"+fileName);
- if (!file.exists()) {
- throw new ServletException("File " + file.getAbsolutePath() + " not found");
- }
- if (getName) {
- doDownloadFilename(request, response, file.getName());
- } else {
- if (isLast) {
- MitoDownloadCache.INSTANCE.removeFileList(fileId); }
- doDownload(request, response, file); file.delete();
- }
上面这个方法是我 copy 来的, 不用研究这个函数的内容, 只看一下结构, 这个函数中既有比较高抽象层次的函数 doDownloadFilename,doDownload, 还有 getFileName 这种位于中间抽象层次的函数, 更有很多像 equals 等较低抽象层次的概念. 混杂了不同抽象层次, 读起来就比较吃力, 现在来改造一下:
- public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- File file = fileToDownLoad(request);
- if (downloadByName(request)) {
- doDownloadFilename(request, response, file.getName());
- } else {
- removeFromCacheIfLast(request);
- doDownload(request, response, fileToDownLoad(request));
- file.delete();
- } }
将功能拆分成红能更小更细的函数, 封装成私有方法, 起一个见名知义的函数名, 使整个方法能够位于同一个抽象层次, 然后在每个函数内部都跟着下一级抽象层次的函数, 这样能给阅读带来极大的便利.(上面函数中私有方法这里不再展示)
函数越小, 功能越集中, 就越容易取一个合适的名字. 另外不要害怕长名称, 描述性很好的长名称比含糊不清的短名称要好. 不要害怕花时间给函数起名字, 起一个合适的名字本身就是编程的一部分.
最理想的函数参数是 0 个 (无参函数), 其次是一个, 二个, 应该尽量避免三个以上的参数.
尽量避免标示参数, 即用一个 boolean 类型作为入参, 比如下面这个函数, eat(true); 这样读起来就有些摸不着头脑, 可能读者需要去查看 eat 方法 eat(boolean isHuntry) 才能比较理解这个函数.
如果函数必须要三个或三个以上的参数, 就说明其中一些参数需要封装成类, 如果很难封装成类, 就说明这个函数的设计有问题.
面向对象语言的方法应该尽量避免使用输出参数, 当然那些 util 类型的工具类除外. this 也有输出函数的意味, 如果函数必须要修改某种状态, 就修改所属对象的状态吧.
try/catch 代码块比较丑陋, 搞乱了代码结构, 最好把 try/catch 代码块的主体部分抽离出来, 另外形成函数.
函数应该只做一件事, 错误处理就是一件事, 因此, 如果处理错误 (带 try/catch) 的函数应该只处理错误, 该函数只有 try/catch 不应该再包含其他内容.
- //实例1:不好的代码格式
- public int size() {
- if (root == null) {
- return 0;
- } else {
- return root.numSiblings();
- }
- }
- //实例1:好的代码风格
- public int size() {
- return root != null ? root.numSiblings() : 0;
- }
- //实例2 不好的代码
- List<Integer> list = Arrays.asList(1, 2, 3));
- list = Collections.unmodifieableList(list);
- return list;
- //实例2 好的代码
- import static java.util.Arrays.*;
- import static java.util.Collections.*;
- ...
- return unmodifiableList(asList(1, 2, 3))
实例 2 中给出的例子有些函数式编程的味道, 这里只是提供一种思路, 但大多数情况我们一般写出来的都是上面的格式.
// 当我写这段代码的时候, 只有老天和我自己知道我在做什么
// 现在, 只剩老天知道了
...
// 我不对以下代码负责
// 使他们逼我写的, 是违背我意愿的
代码结合命名应该良好的描述功能, 写注释说明表达意图的失败以及对代码功能的不自信, 糟糕的代码是注释存在的动机之一.
警告他人 版权协议 公共 API
代码格式很重要, 代码格式关乎沟通, 而沟通是开发者的头等大事.
阅读代码都是从上往下, 从左往右读的. 在一个类中, 在封包声明, 导入声明, 和每个函数之前都应该使用一个空白行来隔开. 这可以给阅读带来愉悦的感受.
空白行隔开了概念, 每个空白行都是一条线索, 标示出一个新的独立的概念, 阅读代码的时候, 我们的目光总是容易停留在空白行的前一行或后一行.
变量声明 : 变量声明应该尽量靠近其使用位置. 类变量应该声明在类的顶部,
相关函数: 某函数 A 调用了函数 B, 应该尽量把他们放到一起, 让 A 位于 B 的上方.
概念相关: 概念相关的代码应该放到一起, 相关性越强, 他们之间的距离就应该越短.
避免过度缩进 (代码梯子):
- //不好的代码风格
- public void startCharging() {
- if (customer.hasFunds()) {
- if (!station.isCharging()) {
- if (!station.currentlyBooked()) {
- reallyStartCharging();
- return;
- }
- }
- }
- throw new UnableToStartException();
- }
- //好的代码风格
- public void startCharging() {
- if (!customer.hasFunds()) throw new UnableToStartException
- if (station.isCharging()) throw new UnableToStartException
- if (station.currentlyBooked()) throw newUnableToStartException reallyStartCharging();
- }
应当遵循无需拖动滚动条到右边的原则, 每行代码控制在 100 个字符以内是良好的风格.
当我们构建一个实体类时, 会为变量设置为 private, 再为变量提供 set/get 方法, 想一想为什么要这么做? 实际上, 即使变量都是私有, 我们通过变量的 set/get 方法操作变量时, 实现仍然被曝光了, 那为何不直接将变量设置为 public, 然后直接操作变量呢
隐藏实现并非只是在变量之间放上一个函数层那么简单, 更大的意义在于抽象, 类并不是简单的用 set/get 方法将变量推向外间, 而是暴露抽象接口, 使得用户无需了解数据的实现就能够操作数据本体.
得墨特定律认为, 模块不应该了解它所操作的对象的内部情况. 对象隐藏数据, 曝光操作.
类 C 的方法 f 只应该调用以下对象的方法
- C
- 由 f 创建的对象
- 作为参数传递给 f 的对象
- 由 C 的实体变量持有的对象
方法不应该调用任何由任何函数返回的对象的方法, 一个很经典的比喻就是: 不要和陌生人说话, 只和朋友说话, 还有一个比喻就是, 人可以命令狗走路, 但人不能直接命令狗的腿去走路, 而应该由狗来控制自己的腿去走路.
如果代码中充斥着 "." 构成的链式编程风格的代码. 意味着该定律被被违反了. 当然如果调用者是数据结构, 没有行为只有数据, 就可以忽略.
来源: http://www.cnblogs.com/fingerboy/p/7411580.html