《CLEAN CODE》读书笔记(六)—— 错误处理

根据异常如何被捕获,将整个事件的错误处理统一代理起来是不错的方法,类似 httpFactory。(7.5 的例子是 Java 的,不太适合本项目)
如果异常打断了业务逻辑,那么,不要返回 null 值,返回 0 或者空数组可以省很多代码,可读性也更高(例子)。

(建议)每个异常应该有环境说明(AngularJs 已经做得很好了)

《CLEAN CODE》读书笔记(五)—— 对象和数据结构

The Law of Demeter 定律认为,模块不应了解它所操作对象的内部情形。
对象的内部结构应该隐藏而不暴露。而数据结构暴露数据,没有明显的行为。
(6.3 的例子:为什么非要暴露 cxt 内部的结构?为什么需要那么多知识来使用 cxt 对象?为了后面能创建指定名称的临时文件,那么直接让 cxt 来做这件事,如何?!)


这章真简洁

《CLEAN CODE》读书笔记(四)—— 代码格式

(非强制)短文件通常比长文件易于理解

向报纸学习:
1、名称简单一目了然;
2、顶部为最高层次的概念和算法;
3、细节往下渐次展开,知道最底层的函数和细节;
4、多数短小精悍,有些稍微长点儿,极少数占满一整页。

(建议)每个空白行都是一条线索,表示出新的独立概念。往下读代码时,目光总会停留于空白行之后那一行。(明显的例子)

(建议)紧密相关的代码相互靠近,暗示它们之间的紧密关系。(被注释分割的例子)

(建议)被调用的函数尽快出现在下面,这样就能轻易找到被调用的函数。

(建议)概念相关的代码同理要靠紧密一些。

缩进,这个没啥好说的。

《Clean Code》读书笔记(三)——Comments

本章宗旨:Don’t comment on bad code, rewrite it. (不要给坏代码写注释,重写它)

注释是使代码变得易读易懂的好习惯。本章作者将视角扩大到注释产生之后的十年的时间数量级内,逐条评判什么是 bad comment 什么不是。事实上,作者在本章更想表达的观点是,注释往往是误导人的,注释的作用往往是比不上符合 clean code (也就是“代码整洁之道”)标准的程序代码本身的。

本章开篇不久就一针见血:“The older the comment is, and the farther away it is from the code it describes”(越久远的注释越可能是误导人的)、“Comments Do Not Make Up for Bad Code”(注释不能弥补难懂的代码,更不能成为编写劣质代码的理由)。除非,注释是以下的情况:1、版权等法律信息;2、提供信息的;3、意图解释;4、给奇怪的参数和返回值的说明,如果只能写得这么奇怪的话(若是写错就起反作用了);5、警告后果;6、要做的事情;7、强调;8、公共 API 的 Javadocs。

接下来详细列举不好的注释。

1)喃喃自语——使读者迷惑;

public void loadProperties()
{
    try
    {
      String propertiesPath = propertiesLocation + "/" + PROPERTIES_FILE;
      FileInputStream propertiesStream = new FileInputStream(propertiesPath);
      loadedProperties.load(propertiesStream);
    }
    catch(IOException e)
    {
       // No properties files means all defaults are loaded
    }
}

2)冗余说明——可有可无,有时还使人迷失在注释里找不到代码

// Utility method that returns when this.closed is true. Throws an exception
// if the timeout is reached.
public synchronized void waitForClose(final long timeoutMillis)
  throws Exception
  {
    if(!closed)
      {
        wait(timeoutMillis);
        if(!closed)
          throw new Exception("MockResponseSender could not be closed");
        }
      }

public abstract class ContainerBase
  implements Container, Lifecycle, Pipeline,
  MBeanRegistration, Serializable {
/**
   * The processor delay for this component.
*/
  protected int backgroundProcessorDelay = -1;
/**
   * The lifecycle event support for this component.
*/
  protected LifecycleSupport lifecycle =
    new LifecycleSupport(this);
/**
   * The container event listeners for this Container.
*/
  protected ArrayList listeners = new ArrayList();
/**  
   * The Loader implementation with which this Container is
   * associated.
*/
  protected Loader loader = null;
/**  
   * The Logger implementation with which this Container is    
   * associated.
*/
  protected Log logger = null;
/**  
   * Associated logger name.
*/
  protected String logName = null

3)误导人的——都知道有什么坏处
不过文中举出了个咋一看很不显著的例子,试图告诉我们这种注释和代码不一致的情况在不经意间就产生了,也就是紧接“2)冗余说明”下的第一段代码。

4)mandated —— 非要给每个变量都加注释

5)像杂志的 —— 每个人想起来就加一行的注释

//现在 SVN 已能很好的管理版本,所以如下注释无任何意义
/**
* Changes (from 11-Oct-2001)
-------------------------- * 
 * 11-Oct-2001 : Re-organised the class and moved it to new package 
 *               com.jrefinery.date (DG);
 * 05-Nov-2001 : Added a getDescription() method, and eliminated NotableDate 
 *               class (DG);
 * 12-Nov-2001 : IBD requires setDescription() method, now that NotableDate 
 *               class is gone (DG);  Changed getPreviousDayOfWeek(), 
 *               getFollowingDayOfWeek() and getNearestDayOfWeek() to correct 
 *               bugs (DG);
 * 05-Dec-2001 : Fixed bug in SpreadsheetDate class (DG);
 * 29-May-2002 : Moved the month constants into a separate interface 
 *               (MonthConstants) (DG);
 * 27-Aug-2002 : Fixed bug in addMonths() method, thanks to N???levka Petr (DG);
 * 03-Oct-2002 : Fixed errors reported by Checkstyle (DG);
 * 13-Mar-2003 : Implemented Serializable (DG);
 * 29-May-2003 : Fixed bug in addMonths method (DG);
 * 04-Sep-2003 : Implemented Comparable.  Updated the isInRange javadocs (DG);
 * 05-Jan-2005 : Fixed bug in addYears() method (1096282) (DG);
**/

6)像噪音的 —— 其结果就是让人直接略过所有注释

《Clean Code》读书笔记(二)——Functions

       《Clean Code》的中文译名是《代码整洁之道》,属于 CnBeta 不久前推荐的程序员必读 11 书之一。适合有一定工作经验的程序员阅读和提升代码质量。书中认为代码的质量的根源还是来自于一种“代码感”。书中会极力用作者及其友人几十年从业经验来阐述何谓“Clean Code”,但是本书仍然无法教会那种“代码感”。

        本文是书中第三章的读书笔记,名为“ Functions ”,讲述函数编程中的“代码整洁之道”。

        本章最重要的一句话应该是:“FUNCTIONS SHOULD DO ONE THING. THEY SHOULD DO IT WELL. THEY SHOULD DO IT ONLY.”(函数应该做一件事。它们要做好那件事。它们只能做那一件事。)

        也就是说,“整洁”的函数代码应该要有一种自明性。这种自明性不来自于注释,而来于使用者看到函数的那几秒钟,能做出瞬间的正确判断。这种瞬间判断不必借助于注释,不必寻找函数的本体,不会引起明显的误会,不会带来多义的困惑。不管是函数本体还是调用,最好只要是个人,看见语句那一瞬,就猜到其作用和参数顺序,而且基本不会猜错。

        如何做好这种自明性呢?书中将宝贵的经验如数家珍,列举出来。

  1. 小!
    要多小呢?2-4行;
  2. blocks and identing
    何意?If、else、While 包含的代码块中只能有一行;
  3. 只做一件事
    函数应该做一件事。它们要做好那件事。它们只能做那一件事。
  4. 同等抽象程度
    函数中不能细节和抽象混淆,就像 .append("\n") 和 getHTML();
  5. Switch 语句
    这是一个必然导致多行、重复等各种问题的语句,处理办法:放入 abstract factory (设计模式的一种);
  6. 描述性命名
    函数的名字是函数功能的描述,可以很长,可以不停改,但同一个意义的用词要一致,参见上一章“meaningful names”;
  7. 参数个数
    最好没有,次好一个,再次两个,避免三个,别用更多;
  8. 通用单参数形式
    避免这两种形式的其它单参数函数的出现,这两种形式是:1)回答问题 boolean fileExists("myFile"); 2)转化传入值并返回 InputStream fileOpen("myFile");
  9. Flag参数
    就是类似这样 render(boolean isNumber)。如 isNumber 为 true ,做一件事。为 false ,做另一件事。这违反了“只做一件事”;
  10. 双参数
    两个参数最好一一对应,缺一不可,前后次序可以预知。比如点的坐标 setPoint(x, y)
  11. 三参数
    只推荐了一种三参数情况 assertEquals(1.0, amount, 0.01),因为可以给用户一个提醒:“浮点数的相等与精确性有关”;
  12. 减少参数的方法
    将有关联的组合成类;
  13. 提示参数顺序的方法
    在函数名字中暗示
  14. 无副作用
    比如 checkPassword() 里就不要包含 Session.initialize() ,不然被误调用的话,用户的 session 就被清空了;
  15. 输出参数
    不要传参然后又改变参数的值,用 参数.方法() 的形式;
  16. 答问和命令分开
    也就是用 if( attributeExists("username") ) 替代 if( set("username", "unclebob") ) ,后者比前者更迷惑人;
  17. 用异常代替错误代码
    当错误代码的 enum 变更时,维护十分繁琐,而且要出错立即处理,错误处理就和主体代码混杂一起。而如果采用 Exception 则符合“只做一件事”的原则,同时免除前述的所有麻烦;
  18. 不要自我重复
    比如三个函数分别对应:任务加载、工作加载和计划加载,三者都 .append(data[x].name).append(data[x].time),这时要设法将重复的这些 .append 写成函数;
  19. 结构化编程
    函数代码较短时,偶尔用多个 renturn, break 或 continue 语句亦可,goto则要避免(话说现在还有人用 goto 么?);
  20. 如何做到上述标准——
    先调试好一个正确运行的代码,然后边调试边重构。

       本文完结。其实 Functions 这一章中,很多和 meaningful names 一章是重复的,比如第 3、9、11 。还有很多是可以相辅相成的,比如 1 和 13。说的玄一些,也许这就是所谓“代码感”——很多条经验都是相互联系的,交错形成一种“感觉”,便是本书所真正想表达的吧!

        看这章的过程中发生了许多趣事。值得一提的是,那时我只看到第四条“同等抽象程度”。于是我费劲的拆我的 JavaScript 代码。然而那是一个给 input Box 绑定自动完成和自动搜索功能下拉框的函数。参数极其多,而我又没意识到,还不敢用全局变量。于是当我沿着函数走的顺序流程,把每个都只有 3 – 4 行的函数的参数一个个从 2 个加到5个之后……彻底风中凌乱了……还好,函数正常运行着,可我再也不想碰它了……

       这大概可以算个典型的生搬硬套的笑话吧!

 

《Clean Code》读书笔记(一):Meaningful Names

《Clean Code》的中译本就是《代码整洁之道》,三天假期看了很多书,今天一起总结了备用。本篇是此书第二章的总结,关于命名法的。

这本书需要仔细研究代码,自己体会。本文只能总结归纳本章的每个点,却不能代替本书的仔细研读。

书中认为,代码写的时间越长,后期维护和修改就越麻烦。越改越乱,越乱生产效率越低下,最后将无可挽回。所以在一开始写代码的时候多花一些时间保持代码的“整洁”(Clean)、一看就懂是相当划算而且有必要的。因为项目越到后期,越绝大部分的时间都将花在阅读代码上面。花在阅读代码上面的时间将决定整个项目的改进速度,从而决定其最终生命力。

写代码和建筑类似,往往赢在细节。作者用自身和朋友们的几十年从业经验,娓娓道来,告诉我们什么叫做“Clean Code”。

命名的重要性就不说了,大家都知道,直接关系到代码的可读性和寿命。以下分点归纳本章内容。

  • Use Intention-Revealing Names

意思就是,与其用 theList 之类的名字,应该换成 flaggedCells 这样有意义的名字。

  • Avoid Disinformation

  1. 在上一条的前提下,有意义的名字不要误导人。比如 accountList 这个名字,如果它指的是一个 String 而不是 List ,那么就不要在名字里出现 List 这个词。
  2. 名字的区别要大一点。比如 XYZMyTodayBrunchBread 和 XYZMyTodayBreakfastBread 区别就太小了……
  3. 相似的东西,拼写相似,便于IDE的自动完成(好像是?这条没懂);
  4. 小写的 l 和 o 用来单独做名字容易引起误会(你懂得,1和0);
  • Make Meaningful Distinctions

命名时,如果表达不同的意思,要有意义的区分之。比如不要一个名为a1的List和一个a2的List,或者Money和TheMoney……(读者可以猜出它们的区别吗?不能就对了)

  • Use Pronounceable Names

就是最好命名可以发音的名字。比如generationTimeStamp如果叫genymdhms,和同事讨论的时候就不太方便。

  • Use Searchable Names

这条涉及到代码中的常量问题。一个名为 MAX_CLASSES_PER_STUDENT 的常量好搜索呢?还是 7 这个数字好搜索呢? sum 这个变量好搜索呢?还是 s 这个变量好搜索呢?

  • Avoid Encodings

编码(确切的说是加密)过的名字,还要花时间来破译,也不好发音。

现代编程语言类型很严格,不需要再增加类型前缀;由于新IDE的便利,成员变量不用加 m_ 前缀;利用设计模式的时候,宁愿用 ShapeFactoryImp ,比 IShapeFactory 好(意思是后者的 I 没有意义,让人费解)。

  • Avoid Mental Mapping

这涉及到循环变量,比如大家都知道 i, j, k 一般是做什么用的,但是多了一个 l 就让人花心思去猜了(这个 l 究竟是一个一般的函数变量呢?还是循环变量呢?还是什么呢?)。

  • Class Names

类名不该是一个动词。

  • Method Names

方法名必须是动宾短语(地球人都知道),重载的构造函数名最好体现其参数的类型和意义。

  • Don’t Be Cute

不要引经据典、自作聪明。比如 DeleteItems 比 HolyHandGrenade 好。

  • Pick One Word per Concept

同一个概念最好保持一致性,使用同一个词代替。比如有人能猜到 DeviceManager 和 ProtocolController 有什么区别么??

  • Don’t Pun

与上一条对应,不同的概念不要用同一个词。

  • Use Solution Domain Names

尽量使用计算机、数学等相关领域的名词,免得同事不停地询问客户该词的含义。

  • Use Problem Domain Names

与上一条对应,当除了待解决问题相关领域内的专业名词之外,无恰当的词汇可用的时候,使用专业领域词汇。这样至少同事可以去问客户,而不是猜测一个自造词汇的含义。

  • Add Meaningful Context

这部分内容分为变量和函数名两部分,在书的第28页。

变量的例子很简单,state 是状态还是州的意思呢?但如果改成  addrState ,就很明显是地址的州的意思。在必要的时候增加变量的语境,可以加强它的自明性。

而函数的例子告诉我们,好的代码不一定要一行一行的看到最后,才明白到底做了什么。例子的代码如下:

if (count == 0){
//代码细节...
//...
}else if (count == 1){
//..
//..
} else {
//..
//..
}

上面显然不如这样的代码更有表现力:

if (count == 0){
   thereAreNoLetters();
} else if (count == 1){
   thereIsOneLetter();
} else {
   thereAreManyLetters(count);
}
  • Don’t Add Gratuitous Context

这个是考虑到现代IDE的自动完成功能带来的便利。假设公司缩写为 GSD,若每个变量都以 GSD 开头,则每次输入 G 都有成百上千个变量名列在下面。为什么要这样为难我们自己呢?

  • Final Words

良好的命名是需要一定的描述水平和同事之间共通的文化背景的。不过,由于现代IDE的便利,改名是一件非常方便的事情,所以不妨和本书作者一样,将自己代码的里的名字改了又改吧!