代码整洁之道--读书总结












系列文章目录













前言

艺术书并不保证你读完之后会成为艺术家,只能告诉你其他艺术家用过的工具,技术和思维过程。













一、有意义的命名

1.1 名副其实 

        注意命名,一旦发现更好的命名,就要立刻替换。当一个变量,方法或类的名称不能告诉你他是用来做什么的,代指什么的,需要用额外的注释来说明,那这个名字就不是名副其实的。

        有时我们会看到一个编写以及各式都规范的代码,但是读起来却比较绕,很可能就是由于变量名没有体现出意义,增加了代码的“模糊度”,增加了阅读的复杂性。

1.2 避免误导

        严禁使用与本意相反的命名,防止误导读者。同时避免使用两个变量名存在较小差距的命名,因为要区分起来相当麻烦,而且有可能造成混淆。千万不要使用字母“l”与字母“O”作为名称,因为这和数字“1”,“0”,几乎分辨不出来。

1.3 做有意义的区分

        如果只为满足编译器或解释器,去命名一些变量方法,就会造成一些不必要的麻烦。例如productInfo与productData的名称虽然不同,但意义却没有区别。一些废话永远不应当出现在变量中,例如nameString中的String以及table不应该出现表名中。

        要区分名称,就要以读者能鉴别出不同之处来区分。

1.4 使用读的出来的名称

        避免一些自造的混乱拼接,而无法读出来的名称。因为读起来很傻~且表意不清楚。        

1.5 使用可搜索的名称

        单字母名称和数字常量有个大问题,很难在一大篇文字中及时找出,例如字母e,它是出现在单词中频率很高的字母。

        窃以为单字母变量只用于短方法的局部变量名,名称长短应与其作用于的大小有关联。如果变量名多次使用,应赋其便于搜索的名称。

1.6 避免使用编码

        匈牙利语标记法 一个字母加一个数字的命名,早期程序员受条件限制使用的,如今没人用了。

        成员前缀 也不必用_m来代表成员变量,应当把类和函数做的足够小,消除对成员前缀的需要。目前使用的编程工具已经实现了使用高亮或颜色来标识出成员变量的功能。

        接口与实现 当接口与实现必须选一个来编码的话,最好是在实现的类名后添加Imp,例如shapeFactoryImp。目前流行写法也是如此。

1.7 避免映射思维

        不应当让读者在脑中把你的名称,翻译成他们熟知的名称,而是直接命名为他们熟知的名称。专业的程序员都了解,明确才是王道。

1.8 类名

        类名和对象名通常为名词或者名词短语,不应当为动词。

1.9 方法名

方法名应当是动词或动词短语,并根据方法作用加上相应的set、get之类的前缀。

1.10 别办可爱

        名称太耍宝,一般只有作者或跟作者一样知道这个梗的人才会知道其中含义。要做到言到意到,意到言到。

1.11 每个概念对应一个词

        给每个抽象概念一个词,并且一以贯之。例如我们对类中常用的getxxx方法,假如一个类中某些的getxxx方法有的命名为fetchxxx或retrievexxx,虽然作用一样,但是容易让人感觉为不同类的不同类型的方法。且使用起来还要特殊记忆某些的方法名命名规则不一致。

1.12 别用双关语

        同一术语用于不同的概念,这就是双关了。我们要避免将同一单词用于不同的目的。如果遵循“一词一意”原则,可能很多类都会出现add方法,但只要这些add方法,在参数列表和返回值上语义等价,就没有问题。但是开发者为了“保持一致”而命名add方法,这就是不可取的,因为这个方法的用意并不是add所表示的含义。

        作者要写出易于理解的代码,写的能让人一目尽览。而不是晦涩难读的。

1.13 使用解决方案领域名称

        只有程序员才会去读代码,每个程序员都知道jobQueue的含义,因此取一些技术性的名称,通常是比较靠谱的。

1.14 使用源自所涉问题领域的名称

        如果不能用专业术语命名,就用所涉及问题领域的名称命名。优秀的程序员和设计师,其工作内容之一就包含区分方案领域和问题领域。

1.15 添加有意义的语境

        很少有名称能自我说明。如果没有良好命名的类,函数或者名称空间来给读者提供语境,那么使用前缀无疑是个好办法。例如name和addrName,后者可以使读者快速理解名称含义。

1.16 不要添加没用语境的

        如开发一个加油站豪华版(Gas Station Deluxe)应用,在每个类的前面加上GSD的前缀,这绝对不是好办法。只要短名称足够清楚就比长名称要好。

Address是个好名称,但是用到端口地址或Web地址的语境下,MAC和URL绝对是更好的选择,可见,命名的精确也很关键。

1.17最后的话

        取好名字最难的地方在于需要有良好的描述技巧和共有的文化背景。与其说这是一种技术、商业或者管理问题,还不如说是一种教学问题。其结果是,这个领域的多数人都没有做好。

        不妨试试上面的这些规则,看你的代码可读性是否有所提升。











二、函数

2.1 短小

        函数的第一要则是短小,第二要则还是短小。大家可能见过祖传代码中的超长函数,对这种函数带来的阅读痛苦是难以忘记的。简短的函数,一眼能看过来的函数,才是函数该有的样子。

代码块和缩进

        if语句、else语句、while语句等代码块应该只有一行,而这一行一般就是一个函数。这样不但能保持函数短小,同时也会让函数更具有说明意义。数应避免各种嵌套,缩进行最好不要超过两层。

2.2 只做一件事

        函数应该做一件事,做好这件事,只做这一件事。如果函数中还能拆出一个函数那么他就不是只做一件事。

函数中的区段

        区段是函数做事太多的明显征兆。只做一件事的函数,无法被合理的划分为多个区段。

2.3 每个函数一个抽象层级

        要确保函数只做一件事,函数中的语句就要都在一个抽象层上。如果函数中混杂着不同的抽象层级,就会让人迷惑,可能导致无法判断出某个表达式是基础概念还是细节。更恶劣的是会加剧基础概念和细节的纠缠。

自顶向下读代码:向下规则

        我们想要自顶向下的阅读代码,就要每个函数后面都跟着下一抽象层级的函数。

2.4 switch语句

           switch语句是我们开发过程中经常遇到的,但是它天生就要多件事。针对此种场景,作者提供了一个将switch语句隐藏的一个抽象类中,但也如作者所说,没有更好的解决这个问题。因此我认为如果不得以使用switch语句,一定要让业务逻辑清晰,同时具备良好的可读性。

2.5 使用描述性名称

        给每个私有方法取一个具有描述性的名称,好名称的价值怎么评价都不为过。函数越短小,功能越集中,越便于取名。不要害怕名称长,也别害怕取名字花时间。命名方式要保持一致,即使用与模块一脉相承的短语、名词和动词给函数命名。

2.6 函数参数

        最理想的参数数量是零,其次是一个,再其次是两个,应避免三个(除非有足够理由)。

2.6.1 一元函数的普遍形式

        函数中传入一个参数有两个常见的形式:一是对这个参数判断,例如 :判断文件是否存在boolean fileExists("fileName")。 二是将该参数进行转化,例如 :将String类型转换为InputStream类型的返回值 InputStream fileopen("myFile")。因此最好使用可以区分上述两种情景的名称。

        还有一种不常见的形式:即返回值为void的函数,实际上是一个事件的处理。这个形式要慎用,而且在命名时尽量让读者了解它是个事件。

        要避免编写不遵循以上规则的一元函数,如果要对输入参数进行转换,要体现在返回值上,

StringBuffer transFrom(StringBuffer in) 要比 void transFrom(StringBuffer out) 更符合规范.

2.6.2 标识参数

        标识参数丑陋不堪,用布尔值作为入参更是奇葩代码。因为标识参数会让你的函数立刻表现出,它不只做一件事。违反了函数只做一件事的原则。

2.6.3 二元函数

        两个参数的函数,要比一个参数的函数难懂。但有的函数天生适合二元函数,例如在确定坐标的函数 piont(x,y)。二元函数会让我们时刻担心入参的顺序。最好是能把二元函数拆解未一元函数。

2.6.4 三元函数

        三元函数的会更加复杂难懂,因此在写之前一定要考虑好。

2.6.5 参数对象

        三个以上的函数,就可以考虑封装成一个类了,从而减少参数。看着像是在为减少参数作弊,但是当一组参数被共同传递时,往往是该有一个名称概念的时候。

2.6.6 参数列表

        要传递数量可变的参数,例如掺入List时,参数就要像上例中那样被对待。同理有可变参数的函数,add(Integer... args ),有可能是一元、二元、或者三元。超过三个犯错的可能性会大大增加。

2.6.7 动词与关键字

        给函数取一个好名字,能更好的解释函数的意图以及参数的顺序。函数名称与参数名应该是非常好的,动词/名词对的形式。例如 writeFiled(name).

2.7 无副作用

        所谓的副作用就是,违反了函数只做一件事的原则,而它要做的另一件事,却被隐藏在这个函数里。形成一种时序耦合或者顺序依赖,进而导致超出开发者调用该函数的原本用意。

观察如下代码:函数目的是匹配正确的用户名以及密码,成功返回true,失败返回false;


但它有一个副作用,就是会调用Session.initialize()方法,会重新初始化。如果开发者的目的只是校验一下用户名和密码是否正确,那调用该方法就会造成抹除当前会话的风险。

这个副作用实际是一种时序耦合,如果一定要用时序耦合,可以修改函数名,增加函数功能的辨识度,提示到开发人员此函数的用法。本例名称可以修改为:checkPasswordAndInitializeSession。

输出参数

        函数中的参数我们一般是作为输入来用的,但是也会出现作为输出的用法,下面这个函数会让人困惑是把s代表的字符串添加到别的字符串后,还是把别的字符串添加到s代表的字符串后。这就需要花时间去调查。

appendStr(s);
//实际函数是,在report后追加
public void appendStr(StringBuffer report);

普遍来说,要避免使用输出参数,如果要必须使用,那就修改所属对象的状态。也就是下面这种写法。

report.appendStr();





 

2.8 分割指令与询问

        不要混淆函数的作用,函数要么回答什么问题,要么做什么事。二者不可得兼。两者放在一起会让读者感到困惑,同时也会产生维护问题。最好的方法就是两个功能分开。

2.9 使用异常代替返回码

2.9.1 抽离try/catch代码块

        try/catch代码块搞乱了代码结构,把错误处理与正常流程混在一起。正确大方法是,将代码快内容单独抽离成函数。这样的代码更容易理解和修改。

2.9.2 错误处理就是一件事

        函数只处理一件事,错误处理就是一件事。因此在catch/finally代码块后,不应该有其他代码。

2.9.3 Error.java 依赖磁铁

        返回错误码,通常暗示有某个类或者枚举定义了错误码

public enum Error{
    OK,
    NO_SUCH,
    LOCKED,
    .....
}

这样的类就是一块依赖磁铁,许多类就要导入和使用它。当Error枚举修改时,所有引用它的类都需要重新编译部署。这就是Error依赖磁铁的负面影响,导致开发人员不愿意添加新的错误类型,而去复用旧的错误编码。

        使用异常代替错误码,新异常就能从异常类派生出来,无需编译和重新部署。

2.10 别重复自己

        重复可能是软件中一切邪恶的根源,很多原则与实践规则都是为了消除重复。加入你有一段逻辑被重复使用四次,那么当这个逻辑修改的话,就要涉及4处修改。这无疑会让遗漏修改的可能性增加3倍。正确的方式,应该将重复的逻辑抽离成一个公用的方法。

2.11 结构化编程

        结构化编程规则:每个函数,函数中的每个代码块,都应该只有一个入口,一个出口。

        这就意味着每个函数只有一个return语句,循环中不能出现break、continue关键字,更不能出现goto关键字。但是,这个规则对小函数几乎没有助益作用。只有在大函数中才会有明显的效果。

2.12 如何写出这样的函数

        有开发经验的伙伴都有一个体会,就是很难一次性把一个函数写的十分优秀。正确的方法是,先实现需求功能,并且编写测试案例,覆盖每一行代码,然后再保持功能不变的情况下,进行重构和优化。

2.13 本章小结

        优秀程序员是把程序当故事来讲,而不是单纯的拼装逻辑。本节的主要讲的是编写良好函数的机制。遵守这些规则,会让你的函数精简有机的结合在一起,帮你讲故事。

三、注释

        注释是一把双刃剑,一方面可以进一步阐释作者意图,另一方面会误导读者。总体上注释的反作用出现的机会更大。

        代码中的注释,存在的实践越久远,他表达的含义与当前的逻辑含义可能差距越大,进而误导开发人员。过多的注释会让程序显得杂论无章。

        开发过程中,要及时更新注释以及删除无效注释。

四、格式

idea工具已具备良好的格式化方式,直接用就好了。

五、对现象和数据格式

5.1 数据抽象

// 案例1
public class point {
    public double x;
    public double y;
}

//案例2
public interface point{
    double getX();
    double getY();
    void setCartesian(double x, double y);
    double getR();
    double getTheta();
    void setPolar(double  r, double  theta);
}

对于处理图形的上述案例中,案例2无疑是更优秀的代码实现。因为案例1暴露了实现,及时它将属性私有化,我们仍然可以通过它提供的取值器与赋值器使用它的变量。案例2的呈现的不只是一种数据结构,还提供了一套存取策略,它隐藏了实现。

隐藏实现表面动作是在变量上加一层函数,而本质思想是对数据进行抽象,以便于对外提供抽象接口,使用户无需了解数据的实现,就能操作数据本体。

数据抽象不只是使用接口、取值器、赋值器,要经过严肃思考,以最佳的方式呈现某个对象包含的数据。

5.2 数据、对象的反对称性

再次回顾5.1中的代码案例,案例1 直接暴露数据,没有提供有意义的函数;案例2将数据隐藏到抽象方法中,提供操作数据的函数。实际上这两种定义的本质是对立的。产生的影响也是深远的。

案例1

 大家可以看出案例1为过程式代码,但并不能因此嘲笑这个写法。因为如果给Geometry类添加一个primeter()方法,那些形状类根本不受影响。另一方面,假如新增加一个图形类,那Geometry类就需要修改。注意这两种情况是对立的。

案例2: 

案例2的写法是常见的面向对象的写法,当添加一个新的图形类时,已存在的类不需要修改任何代码。但是,加入新增一个color()函数时,就需要修改所有的类。这也是对立的!!!

这体现了对象与数据结构的二分原理。

过程式代码便于在不改动既有数据结构的情况下添加新的函数。面向对象便于在不改动既有函数的情况下添加新的类。

因此,面向对象比较难的事,面向过程就比较容易。反之亦然。因此开发过程中要充分考虑适用的业务场景。

5.3 迪米特法则

1)一个对象应该对其它对象保持最少了解

2)类与类的关系越密切,耦合度越大

3)迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何信息

4)迪米特法则还有一个更简单的定义:只与直接朋友通信

5)直接朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合 关系,我们就说这两个对象之间是朋友关系。耦合的方式有很多,依赖,关联,组合,聚合等。其中我们称出现成员变量,方法参数,方法返回值中的列为直接朋友,而出现在局部变量中的类不是直接朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部

5.3.1 火车失事

String final outPutDior = ctxt.getOptions().getScratchDir().getAbsolutPath();

上述写法被称为火车失事,因为看起来像一列火车,这是一种肮脏的写法,应该避免。

正确的写法是,分开写。

5.3.2 结构混乱。

书中提到上述代码的一个案例,即获取文件路径。但是上述写法需要开发人员取了解目标类的内部细节,类的这些方法也暴露了内部细节,这有违背迪米特法则。在读到后面的代码,发现获取绝对路径的目的是创建一个指定名称的文件夹。所以直接让ctxt对来做这件事,显然更好。

 




总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java代码整洁之道是指编写易于理解、易于维护、可扩展性强的高质量代码的一系列准则和实践。以下是一些Java代码整洁之道的要点: 1. 命名规范:使用有意义的变量、方法和类名,遵循驼峰命名法,并避免使用缩写和单个字符作为名称。 2. 函数和方法的简洁性:函数和方法应该尽可能短小,只做一件事,并遵循单一职责原则。避免过长的函数和方法,可以通过提取子函数或方法来减少代码复杂性。 3. 注释和文档:使用清晰的注释来解释代码的意图和逻辑,但不要过度注释。另外,编写良好的文档注释,以便其他开发人员能够理解和使用你的代码。 4. 避免重复代码:重复代码会增加维护成本,应该尽量避免。可以通过提取公共代码块为方法或函数,或者使用继承、接口等方式来实现代码的重用。 5. 异常处理:合理处理异常情况,避免捕获所有异常或忽略异常。根据具体情况选择合适的异常处理方式,例如抛出异常、记录日志或返回默认值。 6. 单元测试:编写单元测试是保证代码质量的重要手段。每个方法都应该有对应的单元测试,覆盖各种情况,确保代码的正确性和稳定性。 7. 代码格式化:统一的代码格式可以提高代码的可读性。使用合适的缩进、空格、换行等格式化规范,并使用代码格式化工具进行自动格式化。 8. 设计模式和面向对象原则:熟悉常用的设计模式和面向对象原则,如单一职责原则、开闭原则、依赖倒置原则等,合理应用于代码设计中。 9. 持续重构:随着需求的变化和代码的演进,及时进行代码重构是保持代码整洁的关键。通过重构,可以改进代码结构、提高可读性和可维护性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值