菜鸟笔记
提升您的技术认知

代码整洁之道(下篇)-ag真人游戏

目录

前言

并发编程

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 map = new hashtable();
 
  public synchronized void putifabsent(k key, v value) {
    if (map.containskey(key))
      map.put(key, value);
  }
}
//3.采用线程安全的合集
concurrenthashmap map = new 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 splines;
 
  public void dive(string reason) {
    saturategradient();
    reticulatesplines();
    diveformoog(reason);
  }
  ...
}
 
//重构后:每个函数产出下一个函数所需结果。
public class moogdiver {
  public void dive(string reason) {
    gradient gradient = saturategradient();
    list splines = reticulatesplines(gradient);
    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.测试
测试不足
使用覆盖率工具
别略过小测试,被忽略的测试就是对不确定事物的疑问
测试边界条件
全面测试相近的缺陷:当某个函数发现缺陷,最好全面测试那个函数
测试应当迅速

网站地图