目录
前言
并发编程
junit框架
重构策略
1. 注释
2.函数
3.一般性问题
3.java和名称
5.测试
前言
本文是《代码整洁之道》读书笔记的下篇,聚焦于并发编程、实战之junit框架重构和重构策略。
上篇地址为: >>>
并发编程
1.并发帮助我们把做什么(目的)和何时做(时机)分解开。多线程可能会导致数据不一致问题。参考:并发编程入门(二):sychronized与线程间通信
2.并发防御原则:
单一权责原则:方法/类/组件应当只有一个修改的理由。建议:分离并发相关代码和其他代码。
限制数据作用域:两个线程修改共享对象同一字段,可能会互相干扰。ag真人游戏的解决方案为sychronized保护共享对象临界区。建议:谨记数据封装,尽可能减少同步区域、严格限制对可能被共享数据的访问。
线程应尽可能独立:每个线程尽量不与其他线程共享数据。
3.java类库:有一些线程安全群集。比如concurrenthashmap、reentrantlock、semaphore和countdownlatch等。
4.hashmap、hashtable和concurrenthashmap:区别。
//重构前:单个方法线程安全,组合线程不安全
if(!hashtable.containskey(somekey)) {
hashtable.put(somekey, new somevalue());
}
//重构后:
//1.锁定hashtable,确定其他使用者做了基于客户端的锁定
synchronized(map) {
if(!map.containskey(key))
map.put(key,value);
}
//2.adapter适配:对象封装hashtable
public class wrappedhashtable
private map
public synchronized void putifabsent(k key, v value) {
if (map.containskey(key))
map.put(key, value);
}
}
//3.采用线程安全的合集
concurrenthashmap
map.putifabsent(key, value);
junit框架
以junit代码为例进行了优化:
原始版本:
public class comparisoncompactor {
private static final string ellipsis = "...";
private static final string delta_end = "]";
private static final string delta_start = "[";
private int fcontextlength;
private string fexpected;
private string factual;
private int fprefix;
private int fsuffix;
public comparisoncompactor(int contextlength,
string expected,
string actual) {
fcontextlength = contextlength;
fexpected = expected;
factual = actual;
}
public string compact(string message) {
if (fexpected == null || factual == null || arestringsequal())
return assert.format(message, fexpected, factual);
findcommonprefix();
findcommonsuffix();
string expected = compactstring(fexpected);
string actual = compactstring(factual);
return assert.format(message, expected, actual);
}
private string compactstring(string source) {
string result = delta_start
source.substring(fprefix, source.length() -
fsuffix 1) delta_end;
if (fprefix > 0)
result = computecommonprefix() result;
if (fsuffix > 0)
result = result computecommonsuffix();
return result;
}
private void findcommonprefix() {
fprefix = 0;
int end = math.min(fexpected.length(), factual.length());
for (; fprefix < end; fprefix ) {
if (fexpected.charat(fprefix) != factual.charat(fprefix))
break;
}
}
private void findcommonsuffix() {
int expectedsuffix = fexpected.length() - 1;
int actualsuffix = factual.length() - 1;
for (;
actualsuffix >= fprefix && expectedsuffix >= fprefix;
actualsuffix--, expectedsuffix--) {
if (fexpected.charat(expectedsuffix) != factual.charat(actualsuffix))
break;
}
fsuffix = fexpected.length() - expectedsuffix;
}
private string computecommonprefix() {
return (fprefix > fcontextlength ? ellipsis : "")
fexpected.substring(math.max(0, fprefix - fcontextlength),
fprefix);
}
private string computecommonsuffix() {
int end = math.min(fexpected.length() - fsuffix 1 fcontextlength,
fexpected.length());
return fexpected.substring(fexpected.length() - fsuffix 1, end)
(fexpected.length() - fsuffix 1 < fexpected.length() -
fcontextlength ? ellipsis : "");
}
private boolean arestringsequal() {
return fexpected.equals(factual);
}
}
重构后版本:
package junit.framework;
public class comparisoncompactor {
private static final string ellipsis = "...";
private static final string delta_end = "]";
private static final string delta_start = "[";
private int contextlength;
private string expected;
private string actual;
private int prefixlength;
private int suffixlength;
public comparisoncompactor(
int contextlength, string expected, string actual
) {
this.contextlength = contextlength;
this.expected = expected;
this.actual = actual;
}
public string formatcompactedcomparison(string message) {
string compactexpected = expected;
string compactactual = actual;
if (shouldbecompacted()) {
findcommonprefixandsuffix();
compactexpected = compact(expected);
compactactual = compact(actual);
}
return assert.format(message, compactexpected, compactactual);
}
private boolean shouldbecompacted() {
return !shouldnotbecompacted();
}
private boolean shouldnotbecompacted() {
return expected == null ||
actual == null ||
expected.equals(actual);
}
private void findcommonprefixandsuffix() {
findcommonprefix();
suffixlength = 0;
for (; !suffixoverlapsprefix(); suffixlength ) {
if (charfromend(expected, suffixlength) !=
charfromend(actual, suffixlength)
)
break;
}
}
private char charfromend(string s, int i) {
return s.charat(s.length() - i - 1);
}
private boolean suffixoverlapsprefix() {
return actual.length() - suffixlength <= prefixlength ||
expected.length() - suffixlength <= prefixlength;
}
private void findcommonprefix() {
prefixlength = 0;
int end = math.min(expected.length(), actual.length());
for (; prefixlength < end; prefixlength )
if (expected.charat(prefixlength) != actual.charat (prefixlength))
break;
}
private string compact(string s) {
return new stringbuilder()
.append(startingellipsis())
.append(startingcontext())
.append(delta_start)
.append(delta(s))
.append(delta_end)
.append(endingcontext())
.append(endingellipsis())
.tostring();
}
private string startingellipsis() {
return prefixlength > contextlength ? ellipsis : "";
}
private string startingcontext() {
int contextstart = math.max(0, prefixlength - contextlength);
int contextend = prefixlength;
return expected.substring(contextstart, contextend);
}
private string delta(string s) {
int deltastart = prefixlength;
int deltaend = s.length() - suffixlength;
return s.substring(deltastart, deltaend);
}
private string endingcontext() {
int contextstart = expected.length() - suffixlength;
int contextend =
math.min(contextstart contextlength, expected.length());
return expected.substring(contextstart, contextend);
}
private string endingellipsis() {
return (suffixlength > contextlength ? ellipsis : "");
}
}
重构点有:
封装条件判断
//优化前
if (expected == null || actual == null || arestringsequal())
return assert.format(message, expected, actual);
//优化后:抽离方法,否定式比肯定式难理解一些。
if (canbecompacted())
return assert.format(message, expected, actual);
private boolean canbecompacted() {
return expected != null && actual != null && !arestringsequal();
}
函数单一职责
//优化前
public string compact(string message) {
if (canbecompacted()) {
findcommonprefix();
findcommonsuffix();
string compactexpected = compactstring(expected);
string compactactual = compactstring(actual);
return assert.format(message, compactexpected, compactactual);
} else {
return assert.format(message, expected, actual);
}
}
private boolean canbecompacted() {
return expected != null && actual != null && !arestringsequal();
}
//优化后:compactxx函数除了压缩,什么也不做。
...
private string compactexpected;
private string compactactual;
...
public string formatcompactedcomparison(string message) {
if (canbecompacted()) {
compactexpectedandactual();
return assert.format(message, compactexpected, compactactual);
} else {
return assert.format(message, expected, actual);
}
}
private void compactexpectedandactual() {
findcommonprefix();
findcommonsuffix();
compactexpected = compactstring(expected);
compactactual = compactstring(actual);
}
时序性耦合问题
//优化前
private void compactexpectedandactual() {
prefixindex = findcommonprefix();
suffixindex = findcommonsuffix(prefixindex);
compactexpected = compactstring(expected);
compactactual = compactstring(actual);
}
private int findcommonsuffix(int prefixindex) {
int expectedsuffix = expected.length() - 1;
int actualsuffix = actual.length() - 1;
for (; actualsuffix >= prefixindex && expectedsuffix >= prefixindex;
actualsuffix--, expectedsuffix--) {
if (expected.charat(expectedsuffix) != actual.charat(actualsuffix))
break;
}
return expected.length() - expectedsuffix;
}
//优化后:findcommonsuffix依赖prefixindex,但后者是由findcommonsuffix计算而来。
private void compactexpectedandactual() {
findcommonprefixandsuffix();
compactexpected = compactstring(expected);
compactactual = compactstring(actual);
}
private void findcommonprefixandsuffix() {
findcommonprefix();
int expectedsuffix = expected.length() - 1;
int actualsuffix = actual.length() - 1;
for (;
actualsuffix >= prefixindex && expectedsuffix >= prefixindex;
actualsuffix--, expectedsuffix--
) {
if (expected.charat(expectedsuffix) != actual.charat(actualsuffix))
break;
}
suffixindex = expected.length() - expectedsuffix;
}
private void findcommonprefix() {
prefixindex = 0;
int end = math.min(expected.length(), actual.length());
for (; prefixindex < end; prefixindex )
if (expected.charat(prefixindex) != actual.charat(prefixindex))
break;
}
代码逻辑优化
//优化后:用charformend的-1替代computecommonsuffix的一堆 1。
private string computecommonsuffix() {
int end = math.min(expected.length() - suffixlength
contextlength, expected.length()
);
return
expected.substring(expected.length() - suffixlength, end)
(expected.length() - suffixlength <
expected.length() - contextlength ?
ellipsis : "");
}
if语句优化
//优化后:删除无用if语句
private string compactstring(string source) {
return
computecommonprefix()
delta_start
source.substring(prefixlength, source.length() - suffixlength)
delta_end
computecommonsuffix();
}
重构策略
1. 注释
不恰当信息:修改时间等易过时信息不应在注释出现,只应该描述代码和设计技术性信息。
废弃的注释:别编写将被废弃的注释,如发现,删除或更新。
冗余注释:注释应当谈及代码未提到的东西。
糟糕的注释:注释应字斟句酌,保持简洁。
注释掉的代码:注释掉的代码,应当删除。
2.函数
过多的参数:尽可能少,没参数最好,避免三个以上的参数。
输出参数:违反直觉,函数非要修改东西状态,就修改他所在对象的状态。
//优化前
appendfooters(s);
//优化后
report.appendfooter();
标识参数:布尔值参数告诉函数不止做了一件事,消灭。
//优化前:
render(boolean issuite)
//优化后
renderforsuite();
renderforsingletest();
死函数:永不调用就删掉。
3.一般性问题
一个源文件中存在多种语言:理想源文件包括且只包括一种语言,比如java源文件不应该把偶偶html、js等;
明显的行为未被实现:函数或类应当符合人类直觉,实现别人期待的行为。
//期望字符串mondy被翻译为day.monday,期望常用缩写能被翻译;期望函数忽略大小写。
day day = daydate.stringtoday(string dayname)
不正确的边界行为:谨小慎微的对待边界条件、极端情况,编写边界条件测试。
忽视安全:忽视安全相当危险,比如try..catch很多代码,某次编译警告等。
重复:找到并消除重复,比如switch...case或if...else改为多态。
在错误的抽象层级上的代码:较低抽象层级概念放在派生类,较高层级概念放在基类。与细节实现有关的常量、变量或者工具函数不应当在基类出现。
信息过多:类中方法越少越好,函数知道的变量越少越好,类拥有的实体变量越少越好。隐藏数据、工具函数、常量和临时变量等,不要创建拥有大量方法/实体变量的类,不要为子类创建大量受保护变量和函数。高内聚、低耦合。
死代码:即不执行的代码,找到并体面埋葬他。
垂直分割:变量与函数应该在靠近被使用的地方定义,私有函数应该刚好在首次被使用的位置下面定义。
前后不一致:比如用response来持有httpservletresponse对象,拿在其他用到httpservletresponse对象的函数应当用同样的变量名。
混淆视听:实现默认构造器可以优化组织。eg:冷启动阶段执行初始化逻辑。
人为耦合:普通的emun不应该包括在特殊类中,花点时间研究什么地方声明函数、变量和常量。不要随手放置。
特性依恋:类的方法只应对所属类的变量和函数感兴趣,不该垂青其他类中的变量和函数。
//特性依恋:消除特型依恋,因为它将一个类的内部情况暴露给另外一个类。calculateweeklypay深入了解了hourlyemployee。
public class hourlypaycalculator {
public money calculateweeklypay(hourlyemployee e) {
int tenthrate = e.gettenthrate().getpennies();
int tenthsworked = e.gettenthsworked();
int straighttime = math.min(400, tenthsworked);
int overtime = math.max(0, tenthsworked - straighttime);
int straightpay = straighttime * tenthrate;
int overtimepay = (int)math.round(overtime*tenthrate*1.5);
return new money(straightpay overtimepay);
}
}
//特型依恋有时不得以而为之,这种情况下特性依恋也可以。
public class hourlyemployeereport {
private hourlyemployee employee ;
public hourlyemployeereport(hourlyemployee e) {
this.employee = e;
}
string reporthours() {
return string.format(
"name: %s\thours:%d.\n",
employee.getname(),
employee.gettenthsworked()/10,
employee.gettenthsworked());
}
}
选择算子参数:将选择算子参数所在的函数改为多个函数。
//优化前
public int calculateweeklypay(boolean overtime) {
int tenthrate = gettenthrate();
int tenthsworked = gettenthsworked();
int straighttime = math.min(400, tenthsworked);
int overtime = math.max(0, tenthsworked - straighttime);
int straightpay = straighttime * tenthrate;
double overtimerate = overtime ? 1.5 : 1.0 * tenthrate;
int overtimepay = (int)math.round(overtime*overtimerate);
return straightpay overtimepay;
}
//优化后
public int straightpay() {
return gettenthsworked() * gettenthrate();
}
public int overtimepay() {
int overtimetenths = math.max(0, gettenthsworked() - 400);
int overtimepay = overtimebonus(overtimetenths);
return straightpay() overtimepay;
}
private int overtimebonus(int overtimetenths) {
double bonus = 0.5 * gettenthrate() * overtimetenths;
return (int) math.round(bonus);
}
晦涩的意图:代码要有表达力,一眼能看出来这玩意干了啥。
//优化前:晦涩难懂
public int m_otcalc() {
return ithswkd * ithsrte
(int) math.round(0.5 * ithsrte *
math.max(0, ithswkd - 400)
);
}
位置错误的权责:函数名称决定其放在哪里。比如gettotalhours应当放在时间统计模块中。
不恰当的静态方法:倾向于选用非静态方法,如果有疑问,用非静态函数,如果的确需要静态函数,确保没机会打算让它有多态行为。
new math().max(a, b)比math.max(double a, double b)愚蠢,因为max用到的全部数据来自于两个参数而非“所属”对象。
hourlypaycalculator.calculatepay(employee, overtimerate)看起来像是个静态函数,并不在特定对象操作&从参数中获取全部数据。但我们可能希望计算每小时支持工资的几种不同算法,比如overtimehourlypaycalculator等,它应该是非静态的。
使用解释性变量:将计算过程打散成有意义的单词变量中放置的中间值。
函数名称应该表达其行为:从函数名称看出函数行为
//优化前
date newdate = date.add(5);
//优化后:返回五天后日期
date newdate = date.increasebydays(5)
理解算法:不敢重构是因为不够理解。
把逻辑依赖改为物理依赖:合适的属性放在合适的位置。
用多态替代if/else或者switch/case。
用命名常量替代魔术数。
准确:代码中的含糊或者不准确要么是意见不统一,要么是懒惰,确保代码是准确的。
封装条件:将解释意图的函数抽离出来。
//重构前:
if (timer.hasexpired() && !timer.isrecurrent())
//重构后:
if (shouldbedeleted(timer))
避免否定条件:尽可能肯定式。
//重构前
if (!buffer.shouldnotcompact())
//重构后
if (buffer.shouldcompact())
函数只做一件事。
//重构前:做了三件事:遍历所有雇员;检查是否该付工资;支付薪水
public void pay() {
for (employee e : employees) {
if (e.ispayday()) {
money pay = e.calculatepay();
e.deliverpay(pay);
}
}
}
//重构后:每个函数只做一件事。
public void pay() {
for (employee e : employees)
payifnecessary(e);
}
private void payifnecessary(employee e) {
if (e.ispayday())
calculateanddeliverpay(e);
}
private void calculateanddeliverpay(employee e) {
money pay = e.calculatepay();
e.deliverpay(pay);
}
不应掩蔽时序耦合,显露调用次序。
//重构前:三个函数次序很重要,捕鱼之前先织网,织网之前先编绳。代码没有强制时序耦合,易产生异常,
public class moogdiver {
gradient gradient;
list
public void dive(string reason) {
saturategradient();
reticulatesplines();
diveformoog(reason);
}
...
}
//重构后:每个函数产出下一个函数所需结果。
public class moogdiver {
public void dive(string reason) {
gradient gradient = saturategradient();
list
diveformoog(splines, reason);
}
...
}
封装边界条件:边界条件代码集中一处,不要散落于代码中。
//重构前
if(level 1 < tags.length) {
parts = new parse(body, tags, level 1, offset endtag);
body = null;
}
//重构后
int nextlevel = level 1;
if(nextlevel < tags.length) {
parts = new parse(body, tags, nextlevel, offset endtag);
body = null;
}
函数应当只在一个抽象层级上:
//重构前:
public string render() throws exception {
stringbuffer html = new stringbuffer("
if(size > 0)
html.append(" size=\"").append(size 1).append("\"");
html.append(">");
return html.tostring();
}
//重构后:
public string render() throws exception {
htmltag hr = new htmltag("hr");
if (extradashes > 0)
hr.addattribute("size", hrsize(extradashes));
return hr.html();
}
private string hrsize(int height) {
int hrsize = height 1;
return string.format("%d", hrsize);
}
在较高层级放置可配置数据。
避免传递浏览。
//重构前:
a.getb().getc().dosomething();
//重构后:
a.dosomething();
3.java和名称
常量和枚举
使用enum而非public static final int。enum学习>>
//优化后:
public class test5 {
public enum color {
red("红色", 1), green("绿色", 2), white("白色", 3), yellow("黄色", 4);
private string name;
private int index;
private color(string name, int index) {
this.name = name;
this.index = index;
}
@override
public string tostring() {
return "color{"
"name='" name '\''
", index=" index
'}';
}
}
public static void main(string[] args) {
system.out.println(color.red.tostring()); // 输出:1-红色
}
}
名称:采用描述性名称,名称应当与抽象层级相符;
//重构前:有可能通过usb、线缆传输,优化策略。
public interface modem {
boolean dial(string phonenumber);
boolean disconnect();
boolean send(char c);
char recv();
string getconnectedphonenumber();
}
//重构后:名称应当与抽象层级相符。
public interface modem {
boolean connect(string connectionlocator);
boolean disconnect();
boolean send(char c);
char recv();
string getconnectedlocator();
}
5.测试
测试不足
使用覆盖率工具
别略过小测试,被忽略的测试就是对不确定事物的疑问
测试边界条件
全面测试相近的缺陷:当某个函数发现缺陷,最好全面测试那个函数
测试应当迅速