如何写出优雅的代码?
本文仅仅是对《代码整洁之道》摘录:
简单代码,重要顺序:
1.能通过所有的测试
2.没有重复代码
3.体现系统中的全部设计理念,提高表达力。
4.包括尽量少的实体,比如类,方法,函数等。
整洁的代码总是看起来像是某位特别在意它的人写的,几乎没有改进的余地,代码作者什么都想到了,如果你企图改进它,总会回到原点,赞叹某人留给你的代码。
一.有意义的命名
1.名副其实的命名
2.避免误导
3.为较大作用范围选用较长名称
二.函数
1.短小的函数
2.一个函数只做一件事
3.自顶而下规则,自向而下读代码。
4.减少过长的switch语句。利用多态。
5.使用描述性的语句
6.函数参数,最好是零参,或一,或二,超过三就要考虑是否可以优化。
7.一元函数,尽量有输入参数,和输出参数。让读者清楚的知道上下文和语境和转换的事件。
8.无副作用。函数只做一个事情。
image.png
如图中的副作用就在于,在一个checkPassword()的函数中做了Session.initialize()的调用。函数名代表修改密码,但是将抹除现有会话数据的逻辑藏了起来。其实,违背了函数只做一件事情的原则。
9.分割指令和询问
函数要么做一件事,要不回答一件事,二者不可兼得。函数应该修改某对象的状态,或者返回改对象的信息,。两样都干会导致混乱。
10.使用try/catch 异常来替代错误码。
11.try/catch 代码块不加修饰,很容易搞乱代码结构,代码丑陋不堪。错误处理和正常流程不能混为一谈。最好把try/catch 代码块的主体抽离出来,形成函数。如下图:
image.png
12.消灭重复代码。
13.减少返回null值。几乎每行代码都在检查null值。这种代码看似不坏,其实糟透了,返回null是在给自己添加工作量,也是在给调用者添乱。只要一处没检查,应用程序就会失控。
14.别传递null值。
15.不要继承常量
三.注释
1.用代码来解释你的逻辑。很显然,我们更希望读到下面这段代码。
image.png
2.好注释。避免废话注释,无意义的注释,不能明确表达函数的注释。
3.代码直接删除,不要注释。其他人不敢删除注释掉的代码,他们会想,代码放在那里,一定有道理,久而久之,注释掉的代码堆积在一起,十分难看。
四.格式
1.向报纸学习。能够让读者从上到下阅读。像报纸一样有头条,告诉故事主题。名称要简单,名称本身应该足够告诉我们是否在正确的模块中,顶部给出高层次概念。细节往下渐次展开。
2.垂直方向上的间隔:封包声明,倒入声明,每个函数之间,空白行隔开。
image.png
3.垂直方向上的靠近
image.png
4.概念相关的代码放在一起,相关性越强,彼此之间的距离就该越短。
5.函数的长度尽量小,100行以内最好。代码行宽度,80以内最好。超过120,就过分了啊。
五.单元测试
TDD三定律:
1.没有测试之前不要写任何功能代码
2.只编写恰好能够体现一个失败情况的测试代码
3.只编写恰好能通过测试的功能代码
整洁的测试遵循5条规则:
1.快速(运行快)
2.独立(每个测试单个运行)
3.可重复(在任何无网络环境中运行测试)
4.自足验证(应有boolean输出,无论是失败或成功)
5.及时(编写测试代码要在生产代码之前及时编写)。
测试代码和生产代码一样重要:
1.测试带来一切好处:让你的代码可扩展,可维护,可复用。有了测试,不用担心对代码的修改,没有测试,每2.次修改都有可能带来缺陷。
3.整洁的代码最主要的就是可读性。以尽可能少的文字表达大量内容。
4.每个测试一个断言
5.每个测试一个概念
六.类
1.类简短,小
2.单一职责原则:只有一条修改的理由,一个类只做一件事。
3.内聚:类应该只有少量的实体变量。类中的每个方法都应该操作一个或多个变量。
4.保持内聚性就会得到许多短小的类
5.为了修改而组织:希望将系统打造成在添加或修改特性时,尽可能少改动其他。我们期望通过扩展系统而非修改现有代码来添加新特性。
6.隔离修改:
image.png
如果系统解耦到足以这样测试的程度。就更灵活和可复用。部件之间的解耦代表着系统中的元素互相隔离的很好。也让对系统的每个元素的理解变得更加容易。
七.系统
1.将系统的构造与使用分开
2.分解main
3.工厂,有时程序也要负责确定何时创建对象
4.依赖注入DI / 控制反转IOC:
对成员变量赋值的控制权,从代码反转到配置文件中(本来new一个实例的工作由开发者反转到Spring容器中)
Person p = new Person();//主动获取方式
反转为:
Person p = ac.getBean("a");//被动获取
5.扩容:横贯式关注面AOP
将程序中的交叉业务逻辑,比如(安全,日志,事务)封装成一个切面。然后,注入到目标对象中(具体的业务逻辑)
在运行时,动态的将代码切入到类的指定方法, 指定位置上的编程思想是面向切面的编程
aop的想法就是将非逻辑部分的代码抽离出来,只考虑逻辑代码就好。
八.并发编程
image.png
并发防御原则:
1.单一职责原则:单一职责原则认为方法和类组件应当只有一个修改的理由。并发设计自身足够复杂到成为修改的理由。分离并发相关代码和其他代码。
2.推论:限制数据作用域
3.推论;使用数据复本
4.线程尽可能独立
5.Java库
image.png
6.执行模型:
image.png
“生产者-消费者模型”:一个或多个生产者线程创建某些工作,并置于缓存或队列中。一个或多个消费者线程从队列中获取并完成这些工作。生产者和消费者之间的队列是一种限定资源。
”读者-作者模型“:当一个主要为读者线程提供信息源,但只偶尔被作者线程更新的共享资源,吞吐量是个问题,增加吞吐量,会导致线程饥饿和过时信息的堆积。更新会影响吞吐量,协调读者线程,不去读作者线程正在更新的信息(反之亦然)。作者线程倾向于长期锁定许多读者线程,从而导致吞吐量问题。
”宴席哲学家“:一群哲学家围着圆桌吃饭,每人左手拿叉,中间放一碗面。但吃饭必须要两个叉。如果左边或右边的哲学家已经取用过一把叉子,中间这位就必须等到别人吃完,放下叉。每位哲学家吃完后,就把两把叉子放回去,直到肚子再饿。
用线程代表哲学家,资源代表叉子。如果没有用心设计,这种竞争系统,就会遭遇死锁,活锁,吞吐量和效率低的问题。
7.同步锁:synchronized ;避免使用一个共享对象的多个方法。有时必须使用一个共享对象的多个方法可以使用一下方案:
“基于客户端的锁定“:客户端在调用第一个方法前锁定服务端,确保锁的范围覆盖了调用最后一个方法的代码
”基于服务端的锁定“;在服务端内创建锁定服务端的方法,调用所有的方法,然后解锁,让客户端调用新的方法
”适配服务端“:创建执行锁定的中间层,这是基于服务端的锁定的例子,但不修改原始服务端代码
8.同步锁:synchronized ;尽可能缩小同步区域
9.加入一个系统的父线程分裂数个子线程,父线程等所有子线程结束,然后,释放资源,关闭。如果,其中一个子线程发生死锁。父线程会一直等待下去,而系统永不会关闭。或者父线程告知全体子线程放弃任务并结束,但如果其中两个子线程正以生产者/消费者模型操作,消费者线程还在等待生产者发来消息,于是锁定在无法接收到关闭信号的状态中,它会死等生产者线程,用不结束,从而导致父线程也无法结束。
此中情况需要编写设计平静关闭的代码,预留时间搞对关闭过程。
解决方案:
1.将伪失败看作可能的线程问题
2.先使非线程代码可工作
3.编写可插拔的线程代码,在不同配置环境下运行。