第十三章《并发编程》感悟(2019.05.29)

为什么要并发

1.并发是一种解耦策略,它帮助我们把做什么(目的)和何时(时机)做分解开

2.解耦目的与时机能明显地改进应用程序的吞吐量和结构

3.单线程程序许多时间花在等待web套接字I/O结束上面,通过采用同时访问多个站点的多线程算法,就能改进性能

常见的迷思和误解
  • 并发总能改进性能:只在多个线程或处理器之间能分享大量等待时间的时候管用
  • 编写并发程序无需修改设计:可能与单线程系统的设计极不相同
  • 在采用web或ejb容器时,理解并发问题并不重要
有关编写并发软件的中肯的说法
  • 并发会在性能和编写额外代码上增加一些开销
  • 正确的并发是复杂的,即使对于简单的问题也是如此
  • 并发缺陷并非总能重现,所以常被看做偶发事件而忽略,未被当做真的缺陷看待
  • 并发常常需要对设计策略的根本性修改
并发防御原则

1.单一权责原则

2.限制数据作用域

3.使用数据副本

4.线程应尽可能独立

了解Java库
  • 使用类库提供的线程安全群集
  • 使用executor框架(executor framework)执行无关任务
  • 尽可能使用非锁定解决方案
  • 有几个类并不是线程安全的
了解执行模型
警惕同步方法之间的依赖
保持同步区域微小
很维编写正确的关闭代码

1.平静关闭很难做到,常见问题与死锁有关,线程一直等待永远不会到来的信号

2.建议:尽早考虑关闭问题,尽早令其工作正常

测试线程代码

1.建议:编写有潜力曝露问题的测试,在不同的编程配置、系统配置和负载条件下频繁运行。如果测试失败,跟踪错误。别因为后来测试通过了后来的运行就忽略失败

2.将伪失败看作可能的线程问题:线程代码导致“不可能失败的”失败,不要将系统错误归咎于偶发事件

3.先使非线程代码可工作:不要同时追踪非线程缺陷和线程缺陷,确保代码在线程之外可工作

4.编写可插拔的线程代码,能在不同的配置环境下运行

5.编写可调整的线程代码:允许线程依据吞吐量和系统使用率自我调整

6.运行多于处理器数量的线程:任务交换越频繁,越有可能找到错过临界区域导致死锁的代码

7.在不同平台上运行:尽早并经常地在所有目标平台上运行线程代码

8.装置试错代码:增加对Object.wait()、Object.sleep()、Object.yield()、Object.priority()等方法的调用,改变代码执行顺序,硬编码或自动化

第十一,二章感悟(2019.05.29)

复杂要人命。它消磨开发者的生命,让产品难以规划、构建和测试。

将构造和使用分开的方法:

(1)分解main,将系统中的全部构造过程搬迁到main或者main模块中:main函数创建对象,再将对象传递给应用程序,应用程序只管使用,对构造一无所知;

(2)如果应用程序需要负责确定何时创建对象,可以创建抽象工厂,让应用程序控制实体创建的时机;

(3)依赖注入,控制反转IoC是依赖管理的手段,它将应用需要的依赖对象的创建权责从对象中拿出来,放在一个专注于此事的对象中,并通过依赖注入(赋值器)将依赖对象传递给应用;

扩容:

面向方面编程(AOP),Java中三种方面和类似方面的机制:代理,纯AOP框架,AspectJ。

(1)java代理:适用于简单情况,如在单独对象或类中包装方法调用。代码量和复杂度是代理的两大弱点。

(2)纯Java AOP框架,如Spring AOP、JBoss AOP

(3)AspectJ:提供将方面作为模块构造处理支持的Java扩展

最佳系统架构由模块化的关注面领域组成,每个关注面均用纯Java(或其他语言)对象实现。不同领域之间用最不具有侵害性的方面或类方面工具整合起来。这种架构能测试驱动,就像代码一样。

拥有模块化关注面的POJO系统提供的敏捷能力,允许我们基于最新的知识做出优化的、时机刚好的决策。决策的复杂性也降低了。

领域特定语言(DSL)允许所有抽象层级和应用程序中的所有领域,从高级策略到底层细节,使用POJO来表达。

跌进

1.简单设计规则

(1)运行所有测试

不可测试的系统不可验证,不可验证的系统,绝不能部署。

(2)不可重复

通过抽取或是模板方法整合重复代码。

(3)表达力

选用好的名称来表达。

(4)尽可能少的类

第十章《类》感悟(2019.05.28)

类的组织

遵循标准的JAVA约定,类应该从一组变量列表开始。变量顺序:公共静态常量,私有静态变量,私有实体变量。很少有公共变量。公共函数应该在变量列表后面。公共函数调用的私有工具函数紧随在该公共函数的后面。

类应该短小

类应该短小
类的名称应该描述其权责,类名正是判断类的长度的第一个手段。如果无法为某个类命以精确的名称,这个类大概就太长了。类名越含混,该类越有可能拥有过多的权责。系统应该由许多短小的类而不是少量巨大的类组成。每个小类封装一个权责,只有一个修改的原因,并与少数其他类一起协同成期望的系统行为。

内聚

内聚性高:类中的方法和变量相互依赖、互相结合成一个逻辑整体。

当类丧失了内聚性,就需要拆分它。将大函数拆分为许多小函数,往往也是将类拆分为许多个小类的时机。程序会更有组织,也会拥有更为透明的结构。

为了修改而组织

在整洁的系统中,对类进行组织,以降低修改的风险(通过扩展而非修改现有的代码来添加新的特性)。

隔离修改:遵循依赖倒置原则(DIP),应该依赖于抽象而非具体细节。

第九章《单元测试》感悟(2019.05.27)

TDD三定律
  • 定律一 在编写不能通过的单元测试前,不可编写生成代码。
  • 定律二 只可编写刚好无法通过的单元测试,不能编译也不算通过。
  • 定律三 只可编写刚好足以通过当前失败测试的生成代码。
保持测试整洁。

作者这里遵循了重要的事情说三遍的原则:可读性,可读性,可读性。如何做到可读,那就是要保证测试代码同其他代码一样,明确,简洁,足具表达力,这也是这本书一直强调的事情。
测试代码和生产代码一样重要。需要被思考,设计和照料。应该和生产代码一样整洁。

每个测试一个断言

在尽可能减少每个概念的断言数量的同时,最好能做到每个测试函数中只测试一个概念。

F.I.R.S.T原则。
  • 快速(Fast) 测试应该够快。
  • 独立(Independent) 测试应该相互独立。
  • 可重复(Repeatable) 测试应当可在任何环境中重复通过。
  • 自足验证(Self-Validating) 测试应该有布尔值输出。
  • 及时(Timely) 测试应及时编写。

第八章《边界》感悟(2019.05.26)

  • 使用第三方代码:使用第三方代码时,如果有边界接口,可将其保留在类或近亲中,避免从公共API中返回边界接口,或者将其边界接口作为参数传给公共API.
  • 浏览和学习边界:在利用第三方程序包时,没有测试第三方代码的职责,但为要使用的第三方代码编写测试,可能最符合我们的利益。不要在生成代码中实验新东西,而是编写测试来遍览和理解第三方代码。
  • 学习性测试的好处不只是免费:学习性测试是一种精确试验,帮助我们增进对api的理解。当第三方程序包发布了新版本,我们可以运行学习性测验,看看程序包的行为有没有改变。学习性测试确保第三方程序包按照我们想要的方式工作。一旦整合进来,就不能保证第三方代码总与我们的需要兼容。如果第三方程序包的修改与测试不兼容,我们也能马上发现。
  • 整洁的边界:在使用我们控制不了的代码时,必须加倍小心,确保未来的修改代价不会太大。边界上的代码需要清晰的分隔和定义了期望的测试。

第六,七章感悟(2019.05.25)

数据抽象

隐藏实现关乎抽象,类并不简单地取值器和赋值器将变量推向外间,而是暴露抽象接口,以便用户无需了解数据的实现就能擦做数据本体。

数据、对象的反对称性

对象把数据隐藏于抽象之后,暴露操作数据的函数。数据结构暴露其数据,没有提供有意义的函数。回头再读一遍,留意这两种定义的本质。他们是对立的,这种差异貌似渺小,但却有深远的意义。对象与数据结构的二分原理:

  • 过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数。面向对象代码便于在不改动既有函数的前提下添加新类。
  • 反过来讲也说得通: 过程式代码难以添加新的数据结构,因为必须修改所有函数。面向对象代码难以添加新函数,因为必须修改所有类。
迪米特法则

模块不应了解它所操作对象的内部情形。对象隐藏数据,暴露操作。者意味着对象不应通过存取其暴露其内部结构,因为这样更像是暴露而非隐藏其内部结构。

数据传送对象

最为精练的数据结构,是一个只有公共变量,没有函数的类。这种数据结构有时被称为数据传送对象,或DTO。

小结

对象暴露行为,隐藏数据。便于添加新对象类型而无需修改既有行为,同时也难以在既有对象添加新行为。数据结构暴露数据,没有明显的行为。便于向既有数据结构添加新行为,同时也难以向既有函数添加数据结构。

错误处理

错误处理很重要,但如果它搞乱了代码逻辑,就是错误的做法。

(1)使用异常而非返回码;

(2)在编写可能抛出异常的代码时,先写try-Catch-Finally语句;

(3)使用不可控异常:C使用可控异常的代价是:违反“开放/闭合原则”。对于一般性 应用开发,其依赖成本要高于收益;

(4)给出异常发生的环境说明:应创建信息充分的错误消息,并和异常一起传递出去,以便判断错误的来源和处所;

(5)依调用者需要定义异常类(看异常如何被捕获):如打包调用API确保返回通用异常类型;

(6)定义常规流程:创建一个类或配置一个对象,用来处理特例;

(7)别返回null值;

(8)别传入null值:在大多数编程语言中,没有良好的方法能对付由调用者意外传入的null值。恰当的做法是,禁止传入null值。

第五章《格式》感悟(2019.05.24)

代码格式关乎沟通,而沟通是专业开发者的头等大事。

垂直格式

阅读代码都是从上往下,从左往右读的.在一个类中,在封包声明,导入声明,和每个函数之前都应该使用一个空白行来隔开.这可以给阅读带来愉悦的感受.
空白行隔开了概念,每个空白行都是一条线索,标示出一个新的独立的概念,阅读代码的时候,我们的目光总是容易停留在空白行的前一行或后一行.

  • 变量声明 :变量声明应该尽量靠近其使用位置.类变量应该声明在类的顶部.
  • 相关函数:某函数A调用了函数B,应该尽量把他们放到一起,让A位于B的上方.
  • 概念相关:概念相关的代码应该放到一起,相关性越强,他们之间的距离就应该越短.
横向格式

一行代码应该多宽?应当遵循无需拖动滚动条到右边的原则,每行代码控制在120个字符以内是良好的风格.

个人风格要与团队风格一致

第四章《注释》感悟(2019.05.23)

注释不能美化糟糕的代码:如果能用代码表达清楚,尽量不要用注释。注释只是在弥补我们在用代码表达意图时遭遇的失败。带有少量注释的整洁而又表达力的代码,比带有大力量注释的零碎而复杂的代码像样的多。与其花时间编写注释糟糕的代码,不如花时间让代码变得更加的整洁。

  • 好的注释:法律信息,提供信息的注释,对意图的解释,阐释,警示,TODO注释(推荐:提醒我们未完成的功能),放大,公共API中的Javadoc
  • 坏的注释:喃喃自语,多余的注释,误导性的注释,循规式注释(利用工具将生成的类和函数注释导致废话连篇),日志式注释(这种冗长的记录只会让模块变得凌乱不堪,应当全部删除),废话注释,可怕的废话,位置标记,括号后面的注释,归属与署名,注释掉的代码,HTML注释,非本地信息,信息过多,不明显的联系,函数头,非公共代码中的Javadoc。

第三章《函数》感悟(2019.05.22)

  • 函数的第一规则是要短小,第二条规则还是要更 短小。
  • 函数应该做一件事。只做这件事。做好这件事。
  • 向函数传入布尔值简直就是骇人听闻的做法。这样做方法签名会立刻变得复杂起来,大声宣布本函数不止做一件事。如果标识为true将会这样做,如果标识为false则会那样做。我们应该把标识为true和false这两种情况写到两个函数中,这样逻辑更清晰.
  • 使用描述性的名称:因为他较好的描述了函数做的事。不要害怕长名字,不要害怕花时间取名字,命名方式要保持一致。
  • 最理想的参数数量是零个,其次是一个,如果函数需要两个、三个或三个以上参数,就说明其中一些参数应该被封装为类了。
  • 无副作用:副作用是一种谎言,函数承诺只做一件事,但是会做其他被藏起来的事。
  • 分割指令与询问:函数要么做什么事,要么回答什么事,但二者不可得兼。函数应该修改某对象的状态,或是返回该对象的有关信息。真正的解决方案是把指令与询问分隔开来,防止混淆的发生。
  • 使用异常代替返回错误码。
  • 别重复自己:重复可能是软件中一切邪恶的根源。

总之:把系统当作故事讲,不是当作程序写。

第二章《有意义的命名》感悟(2019.05.21)

  1. 名副其实:命名要名副其实,不需要用注释来说明就表达出其功能作用。
  2. 避免误导:避免留下掩藏代码本意的错误线索,应当避免使用与本意相悖的词。
  3. 做有意义的区分:废话都是冗余的。
  4. 使用读得出来的名称:如果名称读不出来,讨论的时候就会像个傻鸟。
  5. 使用可搜索的名称:如果常量是个长数字,又被别人错改过,就会逃过搜索,从而造成错误。长名称胜于短名称。
  6. 避免使用编码
# 以前觉得这样命名接口没毛病,一看就知道是接口,读完此文才知道:I被滥用到说好听点是干扰,根本是废话
public interface IUserService {}
  1. 避免思维映射:明确是王道,专业的程序员善用其能,编写其他人能理解的代码。
  2. 类名:类名和对象应该是名称或者名词短语,不应当是动词。
  3. 方法名: 应当是动词或者动词短语。
  4. 每个概念对应一个词:给每个抽象概念选一个词,并且一以贯之。
  5. 别用双关:避免将同意单词用于不同的目的。
  6. 使用解决方案领域的名称:技术性的名称通常是最靠谱的做法。
  7. 添加有意义的语境,不要添加没用的语境。
    以上规则,多多注意,提高代码的可读性。

第一章《整洁的代码》感悟(2019.05.20)

糟糕的代码如同沼泽一般,难于逾越,不仅会毁掉一个团队,更可能会毁掉一个公司。因为勒布朗法则:稍后等于永不。故不要想着回头来优化代码,要先设计好,编写的代码经调整优化测试后方可提交。要有专业的程序员素养和态度。做一个“有代码感”的程序员,从混乱中看出其他的可能与变化,选出最佳方案,指导我们制订修改行动计划,按图索骥。
整洁的代码读起来简单令人愉悦,只做好一件事。每个函数,每个类和每个模块都全神贯注于一事,不受四周细节的干扰和污染。果断决绝,没有不必要的细节。在意自己的代码,对自己的代码负责。学习他人的优点,不要故步自封。不断学习,不断实践,方可做到“让营地比你来时更干净”。