读了代码整洁之道,觉得这本书写的很好,所以就将里面自己觉得很经典的内容记录下来,作为自己以后写代码的标准和准则。同时也为那些曾经困惑过的人一点参考吧!
答:并不是所有的产品都能提出合理的需求,当你面对一个提出不合理需求的产品的时候,你需要坚持自己的原则,不能妥协。
答:易读的代码和易懂的代码是有区别的,不是易读的代码就是易懂的代码。
答:测试驱动代码,你写的代码要可以执行单元测试。如果你发现你的代码很难写单元测试,那么你就要思考你的代码是不是已经不整洁了,或者说已经乱成一团了。
答: 1. 能通过所有测试 2. 没有重复代码 3. 体现系统中全部设计理念 4. 包含尽量少的实体,包括,类,函数,方法等
如果某段代码在程序设计中反复出现,就证明想法在代码中没有很好的体现出来。总之,不要重复代码,只做一件事,表达力,小规模抽象。
变量,函数或类的名称应该已经答复了所有的大问题。它应该告诉你,它为什么会存在,它做什么事情,应该怎么用。如果名称需要注释来补充,那么就不算名副其实。
必须避免留下掩藏代码本意的错误线索。
只要体现出有意义的区分,使用 a 和 the 这样的前缀就没有错。废话就是冗余。
不要用傻乎乎的自造词,而不是恰当的英语词。
使用便于搜索的名字。
把类型或者作用域编进名称里面,徒然增加了解码的负担。没理由要求每位新人都在弄清要应付的代码之外,还要再搞懂另一种编码” 语言”。
不应当让读者在脑中把你的名称翻译为他们熟知的名称,尤其是在经常出现在选择是使用问题领域术语还是解决方案领域术语的时候。
命名一定要通俗易懂。
给每个抽象概念选择一个词,并一以贯之
避免将同一个词用于不同目的,遵循一词一义的原则
在一个 bean 中定义变量名的时候,变量名一定是基于实际使用场景。
函数第一规则是要短小。第二条规则是还要更短小。
确保每个 switch 都埋藏在较低的抽象层级,而且永远不重复。
命名方式要保持一致。使用与模块名一脉相承的短语、名词和动词给函数命名。例如:includeSetupPages。
函数要么做什么事,要么回答什么事,但二者不可兼得
模块不应该了解他所操作对象的内部情形。对象隐藏数据曝露操作,这就意味着对象不应该通过存取器曝露其内部结构。
软件系统应将起始过程和启始过程之后的运行时逻辑分离开,在起始过程中构建应用对象,也会存在相互缠结的依赖关系。(延迟初始化可以加快程序运行速度)
将程序的构造放到 main,并使其与运行时逻辑分开,main 只管构造并将构造后的数据对象发送给各个应用,或者被各个应用使用。但是,不参与运行时的任何逻辑控制。
使用该模式可以将系统构造和系统运行时逻辑分开。
“一开始就做对系统” 纯属神话。反之,我们应该只去实现今天的用户故事,然后重构,明天再扩展系统,实现新用户的故事。这就是迭代和增量敏捷的精髓所在。测试驱动开发,重构以及他们打造出的整洁的代码。
最佳的系统架构由模块化的关注面领域组成,每个关注面均用纯编程语言对象实现。不同的领域之间用最不具有侵害性的方面或类方面工具结合起来。这种架构就能测试驱动,就像代码一样。(需要关注面向切片编程 (POJO))
模块化和关注面切分成就了分散化管理和决策。在巨大的系统中,不管是一座城市或是一个软件项目,无人能做所有的决策。最好是授权给最有资格的人。拥有模块化关注面的 POJO 系统提供的敏捷能力,允许我们基于最新的知识做出优化的,时机刚好的决策。决策的复杂性降低了。
通过这几个原则可以让你的设计变简单(DIP,SRP)
编写的测试越多就越能持续走向编写较易测试的代码,紧耦合的代码很难编写测试。遵循有关编写测试并持续运行测试的简单,明确的规则,系统就会更贴近 OO 低耦合度,高内聚度的目标。编写测试引致更好的设计。
在重构过程中,可以应用有关优秀软件设计的一切知识。提升内聚性,降低耦合度,切分关注面,模块化系统性关注面,缩小函数和类的尺寸,选用更好的名称,如此等等。这也是简单设计原则后三条规则的地方:消除重复,保证表达力,尽可能减少类和方法的数量。
并发是一种解耦策略。它帮我们把做什么和何时做分解开。
并发代码应该从其他代码中分离出来,因为,并发代码相比于其他的代码是很复杂的存在。但是,并发实现细节常常直接嵌入到其他生产代码中。那么下面几个问题需要考虑,
建议:分离并发相关代码与其他代码。
两个线程修改共享对象的同一字段时,可能相互干扰,导致未预期的行为。解决方案之一是采用 synchronized
关键字在代码中保护一块使用共享对象的临界区。限制临界区的数量很重要。更新共享数据的地方越多,就越可能出错。
建议:谨记数据封装,严格限制对可能被共享的数据的访问。
避免共享数据的好方法之一就是一开始就避免共享数据。在某些情况下,有可能复制对象并以只读方式对待。在另外一种情况下,有可能复制对象,从多个线程收集所有副本的结果,并在单个线程中合并这些结果。
让每个线程在自己的世界中存在,不与其他线程共享数据。每个线程处理一个客户端请求,从不共享的源头接纳所有请求数据,存储为本地变量。
建议:尝试将数据分解到可被独立线程操作的独立子集。
一个或多个生产者线程创建某些工作,并置于缓存或队列中。一个或多个消费者线程从队列中获取并完成这些工作。生产者和消费者之间的队列是一种限定资源。
当存在一个主要为读者线程提供信息源,但只偶尔被作者线程更新的共享资源,吞吐量就会是个问题。增加吞吐量,会导致线程饥饿和过时信息的积累。更新会影响吞吐量。协调读者线程,不去读作者线程正在更新的消息,这是一种辛苦的平衡工作。作者线程倾向于长期锁定许多读者线程,从而导致吞吐量问题。
挑战之处在于平衡读者线程和作者线程的需求,实现正确的操作,提供合理的吞吐量,避免线程饥饿。
从哲学家问题可以反映出编写并发经常遇到的问题,死锁,活锁,吞吐量和效率降低等问题。那么学习这些基础问题的解决算法可以帮助你解决大多数的并发问题,因为大多的并发问题都是上述问题的变种。
同步方法之间的依赖会导致并发代码中的狡猾缺陷。Java 语言有 synchronized 概念,可以用来保护单个方法。然而,如果在同一共享类中由多个同步方法,系统就可能写得不太正确了。
建议:避免使用一个共享对象的多个方法。如果有时必须使用一个共享对象的多个方法。这种情况发生时,有3中写对代码的手段:
应该尽可能少地设计临界区,如果有请尽可能减小同步区域。
建议:编写可插拔的线程代码,这样就能在不同的配置环境下运行。
在系统运行时允许线程发生变动。允许线程依据吞吐量和使用率自我调整。
任务交换越频繁,越有可能找到错过临界区或导致死锁的代码。
代码并不是一次就能写的很干净的,需要我们不断的迭代和优化。
让注释传达本该更好地在源代码控制系统,问题追踪系统或任何其他记录系统中保存的信息,是不恰当的。例如,修改历史记录只会用大量过时而无趣的文本搞乱源代码文件。注释只应该描述有关代码和设计的技术性信息。
过时,无关或不正确的注释就是废弃的注释。
如果注释描述的是某种充分自我描述了的东西,那么注释就是多余的。注释应该谈及代码自身没有提到的东西。
构建系统应该是单步的小操作。不应该从源代码控制系统中一小点一小点签出代码。不应该需要一系列神秘指令或环境依赖脚本来构建单个元素。不应该四出寻找额外的小 jar,xml 文件和其他系统所需的杂物。你应该能够用单个命令签出系统,并单个指令构建它。
你应该能够用单个指令就可以运行全部单元测试。能够运行全部测试是如此基础和重要,应该快速,轻易和直接了当地做到。
(1). 如果代码需要有多种语言,那么应该尽力减少源文件中额外语言的数量和范围。
(2). 明显的行为未被实现,如果明显的行为未被实现,读者和用户就不能再依靠他们对函数名称的直觉。他们不再信任原作者,不得不阅读代码细节。
(3). 不正确的边界行为,追索每种边界条件,编写测试。
(4). 忽视安全
(5). 重复,尽可能找到并消除重复。
(6). 在错误的抽象层级上的代码,创建分离较高层级一般性概念与较低层级细节概念的抽象模型,这很重要。
(7). 基类不可多度依赖于派生类,基类应该对派生类一无所知。
(8). 信息过多,设计良好的模块有着非常小的接口,让你事半功倍。设计良好的接口并不提供许多需要依靠的函数,所以耦合度比较低。
优秀的软件开发人员学会限制类或模块中暴露的接口数量。类中的方法越少越好。函数知道的变量越少越好。类拥有的实体变量越少越好。隐藏你的数据。隐藏你的工具函数。隐藏你的常量和你的临时变量。不要创建拥有大量方法或大量实体变量的类。不要为子类创建大量受保护变量和函数。尽力保持接口紧凑。通过限制信息来控制耦合度。
(9). 死代码一定要删除。
(10). 垂直分隔,变量和函数应该在靠近被使用的地方定义。私有函数应该刚好在其首次被使用的位置下面定义。
(11). 前后不一致,从一而终即一旦选中,就小心持续遵循。如果在特定函数中用了 response 的变量来持有 HttpServletResponse 对象,则在其他用到 HttpServletResponse 对象的函数中也用同样的变量名。这样会让你的代码更易阅读。
(12). 混淆视听,没用的变量或者函数直接删掉,以免造成不必要的干扰。
(13). 人为耦合,不互相依赖的东西不该耦合。一般来说,人为耦合是指两个没有直接目的之间的模块的耦合。其根源是将变量,常量或函数不恰当地放在临时方便的位置。
(14). 特性依恋,类的方法只应对其所属类中的变量和函数感兴趣,不该垂青其他类中的变量和函数。当方法通过某个其他对象的访问器和修改器来操作该对象内部数据,则它就依恋于该对象所属类的范围。
(15). 选择算子参数,尽量不要把 boolean 作为函数参数
(16). 晦涩意图,代码要尽可能具有表达力。
(17). 位置错误的权责,我们可以采用最小惊异原则来帮助我们即代码应该放在读者自然而然期待它所在的地方。
(18). 不恰当的静态方法,对于没有机会打算让它有多态行为的函数可以作为动态函数,否则可选用静态函数。
(19). 使用解释性变量,命名要有意义。
(20). 函数名称应该表达其行为
(21). 理解算法
(22). 把逻辑依赖改为物理依赖:
(23). 用多态替代 if/else
或 switch/case
(24). 遵循标准约定
(25). 用命名常量替代魔术数,魔术数不仅仅指的是数字,也泛指任何不能自我描述的符号。
(26). 准确,在可以用 List 的时候,非要把变量声明为 ArrayList 就过分拘束了。在代码中做决定时,确认自己足够准确。明确自己为何要这么做,如果遇到异常情况如何处理。别懒得理会决定的准确性。代码中的含糊和不准确性要么是意见不同的结果,要么源于懒惰。无论原因是什么,都要消除。
(27). 结构甚于约定,坚守结构甚于约定的设计决策。
(28). 封装条件,如果没有 if 或 while 语句的上下文,布尔逻辑就难以理解。应该把解释了条件意图的函数抽离出来。
例如:
if(shouldBeDeleted(timer))
要好于
if(timer.hasExpired() && !timer.isRecurrent())
(29). 避免否定性条件,尽可能将条件表示为肯定形式。
(30). 函数只该做一件事
(31). 掩蔽时序耦合,排列函数参数,好让它们被调用的次序显而易见。
(32). 别随意,构建代码需要理由,而且理由应于代码结构相契合。
(33). 封装边界条件,把处理边界条件的代码集中到一处,不要散落于代码中。
(34). 函数应该只在一个抽象层级上,函数中的语句应该在同意抽象级上,该层级应该是函数名所示操作的下一层。
(35). 在较高层级放置可配置数据,如果你有个已知并在较高抽象层级的默认常量或配置值,不要将它埋藏到较低层级的函数中。把它作为较高层级函数调用较低层级函数时的一个参数。位于较高层级的配置性常量易于修改。它们向下贯穿应用程序。应用程序的较低层级并不拥有这些常量的值。
(36). 避免传递浏览,编写害羞代码。
(37). 不要继承常量
来自:代码整洁之道内容概要