优秀的软件设计,大都关乎分隔——创建合适的空间放置不同种类的代码,对关注面的分隔让代码更易于理解和维护。
1.命名:
(1)有意义(使代码更易读)
(2)避免误导(AccountList,那它就该是个List)
(3)有意义的区分(如一个函数有两个相同类型的参数,命名上做有意义的区分,使我们更方便的使用该函数)
(4)使用读的出来的名字(读的出来,也就更容易理解)
(5)使用可搜索的名字(名称长短应该与其作用域大小相对应,作用域越大,长度也就越长,越不容易重复,作用域很小,可以稍微短些避免麻烦,如for循环变量)
(6)避免使用编码(对于Java这样的强类型语言,现代IDE可以帮我们辨别一个对象的类型)
(7)避免思维映射(使用明确的名称,不要使用自定义的缩写)
(8)类名和对象名使用名词或名词短语,方法名使用动词或者动词短语,并遵守约定
(9)不要使用俚语
(10)每个概念对应一个词,如不要既用Controller,又有Manager。
(11)别用双关语
(12)使用所涉及问题的领域名称:如债权,清算
(13)使用解决问题的方案领域名称:如Factory,Visitor,Decorator等
(14)添加更有意义的语境,使用一个限定词对一个变量做进一步描述,比如addrFirstName,收货地址记录中人的名字。
(15)不要使用没意义的语境
2.函数
(1)编写函数最重要的原则:短小
(2)一个函数只做一件事
(3)每个函数一个抽象层级,函数内的代码应该在同一抽象层级,而不该既有底层的数据层操作,又有上层的业务逻辑。(因为这把更多底层细节的东西带到上层来,让人迷惑)
(4)避免使用switch语句,让switch语句停留在较低层次的语句中,在较高层次用多态来实现。
(5)使用描述性名称
(6)尽可能让函数参数更少,多于3个,应该使用有意义的对象进行封装
(7)函数应该是没有副作用的(如一个验证密码的函数,就不该去创建一个会话,这会让这个函数在其他地方不能完全使用,还是那句话一个函数只做一件事)
(8)分隔指令与查询(避免混淆,一样是,一个函数只做一件事)
(9)使用异常代替错误码(可以将异常代码包装在一个函数内,使代码更美观)
(10)不要重复自己
不必刻意追求刚开始写时,就构思出这样明确的结构,开始时平铺直叙写成一大段,写完了再去打磨它,缩短函数,重新命名,消除重复。
3.注释
注释并不总是好的,不要写喃喃自语,没有意义的注释,另一方面,不能因为我们写了注释,就能给我们写不好代码的罪过开脱。
4.格式
(1)垂直格式
概念相同的代码,位置相近。
函数调用自顶向下。
(2)水平方向
水平对齐,缩进。
5.对象和数据结构
(1)面向结构式编程更容易添加新函数,面向对象类编程更容易添加新类。
反过来说,面向结构编程中添加新数据结构,要改变所有的函数;面向对象编程中添加新函数,则要改变所有的类。
(2) 模块不该了解它所操作对象的内部情形。对象隐藏数据暴露操作。
火车失事:endpoint.getAgentClient().sendRequest(request);
最好修改为:AgentClient client=endpoint.getAgentClient();
client.sendRequest(request);
(3)数据传输对象(DTO)
常用语网络传输的Java Bean
6.错误处理
(1)使用异常,而非错误返回码
(2)使用不可控异常(Unchecked Exception)
(3)根据情况定义异常类
(4)不要返回null
(5)不要传入null
7.边界
(1)对第三方类库进行包装,使用第三方类库的时候,第三方类库的某各类提供了更多的方法,我们可以对其进行包装,避免其暴露过多的功能
(2)使用新的第三方类库,我们可以编写学习性测试,既可以帮助我们熟悉该类库,又可以在升级版本时,验证新版本对现有功能是否有影响。
(3)适配器模式
8.单元测试
9.类
(1)如何组织?
顺序:静态公有变量→静态私有变量→私有实体变量(尽量别用公有变量)
尽可能保持变量、工具方法的封装性。
(2)短小,与函数不同的是,我们不从行数判断,而从这个类是否遵循了单一权责判断(其实函数也要做到单一职责吧)
(3)遵循权责单一(SRP,Single Responsibility Principle)。
SRP认为类或模块只有一个修改它的理由。
SRP是面向对象最重要的原则之一。我们在编程时难以兼顾进度和整洁,所以我们不能完成功能就万事大吉,而是要在完成功能后,对代码进行整理。
(4)内聚:一个模块内部各个成分的关联程度。更形象的说,类内的方法使用越多的属性,这个方法就越内聚,极致的内聚不可能也不可取,但我们追求高内聚。保持内聚性,也有助于我们得到短小的类。我们要写内聚较高的类。
(5)隔离变化:
每个类只有一个修改它的理由,还是SRP。
隔离修改:DIP(依赖倒置原则)其实就是一种隔离变化,依赖于抽象而不是细节,耦合更低。
10.系统
讲了IOC和AOP,刚开始觉得看着有点烦躁,没有仔细看,端午假期静心看了下这章,发现给了我很多启发。
(1)将系统构造和使用分离开来,在我们日常的web开发中,Spring帮助我们做了这件事,虽然冗长的xml文件或许不是很友好,但它帮我们做到了类的职责单一,保持了业务类专注于业务逻辑,避免繁杂的启动代码的困扰。进一步Spring Boot帮助我们解决了程式化的组件装配。
Spring的IOC功能实际上是帮助我们更简便容易地实践SRP(单一职责原则)和DIP(反向依赖原则)这两个原则。
(2)Spring AOP这个功能要好理解一些,利用动态代理将一些共性的代码抽离出来作为关注面。
11.迭进
(1)简单设计的四条规则:运行所有测试,不可重复,表达程序员的意图,尽可能减少类和方法的数量。
12.并发编程
(1)并发的目的:1.提升响应速度和吞吐量。2.解耦
(2)关于并发编程的一些误解:1.单元测试总能改进性能。2.编写并发程序无需修改设计。3.采用web容器时,理解并发问题不重要。
(3)并发防御原则:
1.单一权责原则(SRP)
2.限制数据作用域
3.使用数据副本,避免共享数据
4.线程尽可能的独立
(4)几个概念:
限定资源:有固定尺寸或数量的资源,如数据库连接,固定尺寸读/写缓存
互斥:每一时刻仅有一个线程能够访问资源
线程饥饿:长时间得不到执行。
死锁:几个线程持有彼此需要的资源,互相等待,不能继续执行。
活锁:执行一致的线程,每个都想起步,但每次都发现资源被其他线程占有。
(5)执行模型
我们遇到的问题大多是这三种模型的变形。
1.生产者-消费者模型:
队列或者缓存成为生产者、消费者之间的限定资源。
2.读者-作者模型
重要的是平衡二者的线程,避免线程饥饿。
当有线程正在读的时候,不允许写 线程写,但是允许其他的读线程进行读。有写线程正在写的时候,其他的线程不应该读写。为了防止写线程出现饥饿现象,当线程正在读,如果写线程请求写,那么应该禁止再来的读线程进行读。
3.哲学家就餐模型
进程之间资源竞争的问题,会出现死锁,活锁,吞吐量和效率降低等问题。
要么两个叉子都拿,要么都不拿。
13.逐步改进
14JUnit
15.味道与启发