重构 第1章 重构,第一个案例
来源:互联网 发布:录音配乐软件 编辑:程序博客网 时间:2024/04/28 23:07
本章通过1个案例,逐步的感受下重构
案例说明
这是一个非常简单的案例,展示了一个影片出租店用的程序,计算每一位顾客的消费金额并打印详单的模块,同时还需要计算每一位客人的积分。
抽象了三个实体:影片(片名、片类型)、租赁(影片、租赁天数)、顾客(姓名、租赁清单),
代码详见下面的三个类以及测试类。
customer.java
mport java.util.Enumeration;import java.util.Vector;public class Customer { private String name; private Vector<Rental> rentals = new Vector<>(); public Customer(String name) { this.name = name; } public String getName() { return name; } public void addRental(Rental rental) { rentals.add(rental); } public String statement() { double totalAmount = 0; int frequentRenterPointers = 0; Enumeration<Rental> rentalEnumeration = rentals.elements(); String result = "Rental Records for " + getName() + "\n"; while (rentalEnumeration.hasMoreElements()) { double thisAmount = 0; Rental each = rentalEnumeration.nextElement(); // determine amounts for each line switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: thisAmount += 2; if (each.getDayRented() > 2) { thisAmount += (each.getDayRented() - 2) * 1.5; } break; case Movie.NEW_RELEASE: thisAmount += each.getDayRented() * 3; break; case Movie.CHILDREN: thisAmount += 1.5; if (each.getDayRented() > 3) { thisAmount += (each.getDayRented() - 3) * 1.5; } break; } // add frequent renter points frequentRenterPointers++; // add bonus for two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDayRented() > 1) { frequentRenterPointers++; } //show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + thisAmount + "\n"; totalAmount += thisAmount; } // add footer lines result += "Amount owed is " + totalAmount + "\n"; result += "You earned " + frequentRenterPointers + " frequent renter points"; return result; }}Movie.java
public class Movie { public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; public static final int CHILDREN = 2; private String title; private int priceCode; public Movie(String title, int priceCode) { this.title = title; this.priceCode = priceCode; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public int getPriceCode() { return priceCode; } public void setPriceCode(int priceCode) { this.priceCode = priceCode; }}
public class Rental { private Movie movie; private int dayRented; public Movie getMovie() { return movie; } public int getDayRented() { return dayRented; } public Rental(Movie movie, int dayRented) { this.movie = movie; this.dayRented = dayRented; }}
import org.junit.Assert;public class TestOutput { @org.junit.Test public void Should_get_correct_statement() { // 三部影片 Movie movie1 = new Movie("xxxxx", Movie.NEW_RELEASE); Movie movie2 = new Movie("AAAAA", Movie.CHILDREN); Movie movie3 = new Movie("DDDDD", Movie.REGULAR); //两名顾客 Customer customer1 = new Customer("H1212"); Customer customer2 = new Customer("J1212"); //顾客租约 Rental rental1 = new Rental(movie1, 10); Rental rental2 = new Rental(movie2, 1); Rental rental3 = new Rental(movie3, 3); customer1.addRental(rental1); customer1.addRental(rental2); customer2.addRental(rental2); customer2.addRental(rental3); Assert.assertEquals(customer1.statement(), "Rental Records for H1212\n" + "\txxxxx\t30.0\n" + "\tAAAAA\t1.5\n" + "Amount owed is 31.5\n" + "You earned 3 frequent renter points"); Assert.assertEquals(customer2.statement(), "Rental Records for J1212\n" + "\tAAAAA\t1.5\n" + "\tDDDDD\t3.5\n" + "Amount owed is 5.0\n" + "You earned 2 frequent renter points"); }}Customer提供了 statement 方法来输出租赁清单信息 ;
如果你发现自己需要为程序添加一个特性,而代码结构使你无法方便达成目的,那就重构那个程序,使特性的添加比较容易进行,然后再添加特性。
1 重构第一步,准备测试类
重构的第一步就是要为即将修改的代码建立一组可靠的测试环境,这些测试是必要的,避免因为重构带来新的 bug。
测试类已经在上面写好了。
重构之前,首先检查自己是否有一套可靠的测试机制,这些测试必须有自我检验的能力。
其中我们发现statement 方法(Long Method),需要分解长函数,把较小的代码移至更合适的地方。重构 statement 可以分以下几个步骤:
2 提炼 Switch 语句,提炼到单独的函数中比较合适。
注意两个临时变量,thisAmount 和 each,需要处理;任何一个傻瓜都能写出计算机可以理解的代码,唯有写出人类容易理解的代码,才是优秀的程序员。
提炼amountFor函数:
import java.util.Enumeration;import java.util.Vector;public class Customer { private String name; private Vector<Rental> rentals = new Vector<>(); public Customer(String name) { this.name = name; } public String getName() { return name; } public void addRental(Rental rental) { rentals.add(rental); } public String statement() { double totalAmount = 0; int frequentRenterPointers = 0; Enumeration<Rental> rentalEnumeration = rentals.elements(); String result = "Rental Records for " + getName() + "\n"; while (rentalEnumeration.hasMoreElements()) { double thisAmount = 0; Rental each = rentalEnumeration.nextElement(); thisAmount = amountFor(each); // add frequent renter points frequentRenterPointers++; // add bonus for two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDayRented() > 1) { frequentRenterPointers++; } //show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + thisAmount + "\n"; totalAmount += thisAmount; } // add footer lines result += "Amount owed is " + totalAmount + "\n"; result += "You earned " + frequentRenterPointers + " frequent renter points"; return result; } private double amountFor(Rental each) { double thisAmount = 0; switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: thisAmount += 2; if (each.getDayRented() > 2) { thisAmount += (each.getDayRented() - 2) * 1.5; } break; case Movie.NEW_RELEASE: thisAmount += each.getDayRented() * 3; break; case Movie.CHILDREN: thisAmount += 1.5; if (each.getDayRented() > 3) { thisAmount += (each.getDayRented() - 3) * 1.5; } break; } return thisAmount; }}
3 修改变量名称:amountFor内的变量名称
import java.util.Enumeration;import java.util.Vector;public class Customer { private String name; private Vector<Rental> rentals = new Vector<>(); public Customer(String name) { this.name = name; } public String getName() { return name; } public void addRental(Rental rental) { rentals.add(rental); } public String statement() { double totalAmount = 0; int frequentRenterPointers = 0; Enumeration<Rental> rentalEnumeration = rentals.elements(); String result = "Rental Records for " + getName() + "\n"; while (rentalEnumeration.hasMoreElements()) { double thisAmount = 0; Rental each = rentalEnumeration.nextElement(); thisAmount = amountFor(each); // add frequent renter points frequentRenterPointers++; // add bonus for two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDayRented() > 1) { frequentRenterPointers++; } //show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + thisAmount + "\n"; totalAmount += thisAmount; } // add footer lines result += "Amount owed is " + totalAmount + "\n"; result += "You earned " + frequentRenterPointers + " frequent renter points"; return result; } private double amountFor(Rental aRental) { double result = 0; switch (aRental.getMovie().getPriceCode()) { case Movie.REGULAR: result += 2; if (aRental.getDayRented() > 2) { result += (aRental.getDayRented() - 2) * 1.5; } break; case Movie.NEW_RELEASE: result += aRental.getDayRented() * 3; break; case Movie.CHILDREN: result += 1.5; if (aRental.getDayRented() > 3) { result += (aRental.getDayRented() - 3) * 1.5; } break; } return result; }}
4 搬移金额代码:amountFor使用的更多的是来自Rental类的信息,将其移动到rental类
import java.util.Enumeration;import java.util.Vector;public class Customer { private String name; private Vector<Rental> rentals = new Vector<>(); public Customer(String name) { this.name = name; } public String getName() { return name; } public void addRental(Rental rental) { rentals.add(rental); } public String statement() { double totalAmount = 0; int frequentRenterPointers = 0; Enumeration<Rental> rentalEnumeration = rentals.elements(); String result = "Rental Records for " + getName() + "\n"; while (rentalEnumeration.hasMoreElements()) { double thisAmount = 0; Rental each = rentalEnumeration.nextElement(); thisAmount = amountFor(each); // add frequent renter points frequentRenterPointers++; // add bonus for two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDayRented() > 1) { frequentRenterPointers++; } //show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + thisAmount + "\n"; totalAmount += thisAmount; } // add footer lines result += "Amount owed is " + totalAmount + "\n"; result += "You earned " + frequentRenterPointers + " frequent renter points"; return result; } private double amountFor(Rental aRental) { return aRental.getCharge(); }}public class Rental { private Movie movie; private int dayRented; public Movie getMovie() { return movie; } public int getDayRented() { return dayRented; } public Rental(Movie movie, int dayRented) { this.movie = movie; this.dayRented = dayRented; } public double getCharge() { double result = 0; switch (getMovie().getPriceCode()) { case Movie.REGULAR: result += 2; if (getDayRented() > 2) { result += (getDayRented() - 2) * 1.5; } break; case Movie.NEW_RELEASE: result += getDayRented() * 3; break; case Movie.CHILDREN: result += 1.5; if (getDayRented() > 3) { result += (getDayRented() - 3) * 1.5; } break; } return result; }}
5 寻找旧函数的所有引用点,删除无用的旧函数,其实就是内联函数:cmd+option+N
import java.util.Enumeration;import java.util.Vector;public class Customer { private String name; private Vector<Rental> rentals = new Vector<>(); public Customer(String name) { this.name = name; } public String getName() { return name; } public void addRental(Rental rental) { rentals.add(rental); } public String statement() { double totalAmount = 0; int frequentRenterPointers = 0; Enumeration<Rental> rentalEnumeration = rentals.elements(); String result = "Rental Records for " + getName() + "\n"; while (rentalEnumeration.hasMoreElements()) { double thisAmount = 0; Rental each = rentalEnumeration.nextElement(); thisAmount = each.getCharge(); // add frequent renter points frequentRenterPointers++; // add bonus for two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDayRented() > 1) { frequentRenterPointers++; } //show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + thisAmount + "\n"; totalAmount += thisAmount; } // add footer lines result += "Amount owed is " + totalAmount + "\n"; result += "You earned " + frequentRenterPointers + " frequent renter points"; return result; }}
6 继续内联,删除无用的临时变量
删除无用的临时变量, thisAmount 只是接受了一下 getCharge(),后面没有作任何变化,可以采用以查询替换变量原则去除变量 thisAmount,暂时先不讨论两次计算带来的性能开销,这个完全可以优化掉。
import java.util.Enumeration;import java.util.Vector;public class Customer { private String name; private Vector<Rental> rentals = new Vector<>(); public Customer(String name) { this.name = name; } public String getName() { return name; } public void addRental(Rental rental) { rentals.add(rental); } public String statement() { double totalAmount = 0; int frequentRenterPointers = 0; Enumeration<Rental> rentalEnumeration = rentals.elements(); String result = "Rental Records for " + getName() + "\n"; while (rentalEnumeration.hasMoreElements()) { Rental each = rentalEnumeration.nextElement(); // add frequent renter points frequentRenterPointers++; // add bonus for two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDayRented() > 1) { frequentRenterPointers++; } //show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + each.getCharge() + "\n"; totalAmount += each.getCharge(); } // add footer lines result += "Amount owed is " + totalAmount + "\n"; result += "You earned " + frequentRenterPointers + " frequent renter points"; return result; }}
7 提炼”常客积分计算“代码,FrequentRenterPoint
提炼完成函数后,放入Rental类中;
import java.util.Enumeration;import java.util.Vector;public class Customer { private String name; private Vector<Rental> rentals = new Vector<>(); public Customer(String name) { this.name = name; } public String getName() { return name; } public void addRental(Rental rental) { rentals.add(rental); } public String statement() { double totalAmount = 0; int frequentRenterPointers = 0; Enumeration<Rental> rentalEnumeration = rentals.elements(); String result = "Rental Records for " + getName() + "\n"; while (rentalEnumeration.hasMoreElements()) { Rental each = rentalEnumeration.nextElement(); frequentRenterPointers += each.getFrequentRenterPoints(); //show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + each.getCharge() + "\n"; totalAmount += each.getCharge(); } // add footer lines result += "Amount owed is " + totalAmount + "\n"; result += "You earned " + frequentRenterPointers + " frequent renter points"; return result; }}public class Rental { private Movie movie; private int dayRented; public Movie getMovie() { return movie; } public int getDayRented() { return dayRented; } public Rental(Movie movie, int dayRented) { this.movie = movie; this.dayRented = dayRented; } public double getCharge() { double result = 0; switch (getMovie().getPriceCode()) { case Movie.REGULAR: result += 2; if (getDayRented() > 2) { result += (getDayRented() - 2) * 1.5; } break; case Movie.NEW_RELEASE: result += getDayRented() * 3; break; case Movie.CHILDREN: result += 1.5; if (getDayRented() > 3) { result += (getDayRented() - 3) * 1.5; } break; } return result; } int getFrequentRenterPoints() { if ((getMovie().getPriceCode() == Movie.NEW_RELEASE) && getDayRented() > 1) { return 2; } return 1;//不喜欢用else }}
8 去除临时变量,使用查询代替
去除totalAmount和frequentRenterPointers 临时变量:经过上述重构之后,发现临时变量依旧是一个灾难,就需要再使用用查询代替临时变量(Replace Temp With Query)的手法来清理这两个变量:
totalAmount:
import java.util.Enumeration;import java.util.Vector;public class Customer { private String name; private Vector<Rental> rentals = new Vector<>(); public Customer(String name) { this.name = name; } public String getName() { return name; } public void addRental(Rental rental) { rentals.add(rental); } public String statement() { int frequentRenterPointers = 0; Enumeration<Rental> rentalEnumeration = rentals.elements(); String result = "Rental Records for " + getName() + "\n"; while (rentalEnumeration.hasMoreElements()) { Rental each = rentalEnumeration.nextElement(); frequentRenterPointers += each.getFrequentRenterPoints(); //show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + each.getCharge() + "\n"; } // add footer lines result += "Amount owed is " + getTotalCharge() + "\n"; result += "You earned " + frequentRenterPointers + " frequent renter points"; return result; } private double getTotalCharge() { double result = 0; Enumeration rentalList = rentals.elements(); while (rentalList.hasMoreElements()) { Rental each = (Rental) rentalList.nextElement(); result += each.getCharge(); } return result; }}frequentRenterPointers:
import java.util.Enumeration;import java.util.Vector;public class Customer { private String name; private Vector<Rental> rentals = new Vector<>(); public Customer(String name) { this.name = name; } public String getName() { return name; } public void addRental(Rental rental) { rentals.add(rental); } public String statement() { Enumeration<Rental> rentalEnumeration = rentals.elements(); String result = "Rental Records for " + getName() + "\n"; while (rentalEnumeration.hasMoreElements()) { Rental each = rentalEnumeration.nextElement(); //show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + each.getCharge() + "\n"; } // add footer lines result += "Amount owed is " + getTotalCharge() + "\n"; result += "You earned " + getFrequentRenterPointers() + " frequent renter points"; return result; } private double getTotalCharge() { double result = 0; Enumeration rentalList = rentals.elements(); while (rentalList.hasMoreElements()) { Rental each = (Rental) rentalList.nextElement(); result += each.getCharge(); } return result; } private int getFrequentRenterPointers() { int result = 0; Enumeration rentalList = rentals.elements(); while (rentalList.hasMoreElements()) { Rental each = (Rental) rentalList.nextElement(); result += each.getFrequentRenterPoints(); } return result; }}
9 switch语句的移动
switch应该放在正确位置:根据movie选择,所以放到movie中最好不要在另一个对象的属性基础上运用Switch语句,如果不得不用,也应该咋对象自己的数据上使用,而不是别人的身上;
public class Movie { public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; public static final int CHILDREN = 2; private String title; private int priceCode; public Movie(String title, int priceCode) { this.title = title; this.priceCode = priceCode; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public int getPriceCode() { return priceCode; } public void setPriceCode(int priceCode) { this.priceCode = priceCode; } public double getCharge(int dayRented) { double result = 0; switch (getPriceCode()) { case Movie.REGULAR: result += 2; if (dayRented > 2) { result += (dayRented - 2) * 1.5; } break; case Movie.NEW_RELEASE: result += dayRented * 3; break; case Movie.CHILDREN: result += 1.5; if (dayRented > 3) { result += (dayRented - 3) * 1.5; } break; } return result; } public int getFrequentRenterPoints(int dayRented) { if ((getPriceCode() == Movie.NEW_RELEASE) && dayRented > 1) { return 2; } return 1;//不喜欢用else }}public class Rental { private Movie movie; private int dayRented; public Movie getMovie() { return movie; } public int getDayRented() { return dayRented; } public Rental(Movie movie, int dayRented) { this.movie = movie; this.dayRented = dayRented; } public double getCharge() { return movie.getCharge(dayRented); } int getFrequentRenterPoints() { return movie.getFrequentRenterPoints(dayRented); }}
10 继承体系
创建price,分别不用类型继承;
public class Movie { public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; public static final int CHILDREN = 2; private String title; private int priceCode; private Price price; public Movie(String title, int priceCode) { this.title = title; setPriceCode(priceCode); } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public int getPriceCode() { return price.getPriceCode(); } public void setPriceCode(int priceCode) { switch (priceCode) { case REGULAR: price = new RegularPrice(); break; case CHILDREN: price = new ChildrensPrice(); break; case NEW_RELEASE: price = new NewReleasePrice(); break; default: throw new IllegalArgumentException("Incorrect price code"); } } public double getCharge(int dayRented) { double result = 0; switch (getPriceCode()) { case Movie.REGULAR: result += 2; if (dayRented > 2) { result += (dayRented - 2) * 1.5; } break; case Movie.NEW_RELEASE: result += dayRented * 3; break; case Movie.CHILDREN: result += 1.5; if (dayRented > 3) { result += (dayRented - 3) * 1.5; } break; } return result; } public int getFrequentRenterPoints(int dayRented) { if ((getPriceCode() == Movie.NEW_RELEASE) && dayRented > 1) { return 2; } return 1;//不喜欢用else }}public abstract class Price { abstract int getPriceCode();}class ChildrensPrice extends Price { @Override int getPriceCode() { return Movie.CHILDREN; }}class NewReleasePrice extends Price { @Override int getPriceCode() { return Movie.NEW_RELEASE; }}class RegularPrice extends Price { @Override int getPriceCode() { return Movie.REGULAR; }}
11 多态取代Switch,创建price及子类
在price类加抽象函数,并在所有子类中加上对应的具体函数;
public abstract class Price { abstract int getPriceCode(); abstract double getCharge(int dayRenter);}class ChildrensPrice extends Price { @Override int getPriceCode() { return Movie.CHILDREN; } @Override double getCharge(int dayRented) { double result = 1.5; if (dayRented > 3) { result += (dayRented - 3) * 1.5; } return result; }}class NewReleasePrice extends Price { @Override int getPriceCode() { return Movie.NEW_RELEASE; } @Override double getCharge(int dayRented) { return dayRented * 3; }}class RegularPrice extends Price { @Override int getPriceCode() { return Movie.REGULAR; } @Override double getCharge(int dayRented) { double result = 2; if (dayRented > 2) { result += (dayRented - 2) * 1.5; } return result; }}import org.junit.Before;public class Movie { public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; public static final int CHILDREN = 2; private String title; private int priceCode; private Price price; public Movie(String title, int priceCode) { this.title = title; setPriceCode(priceCode); } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public int getPriceCode() { return price.getPriceCode(); } public void setPriceCode(int priceCode) { switch (priceCode) { case REGULAR: price = new RegularPrice(); break; case CHILDREN: price = new ChildrensPrice(); break; case NEW_RELEASE: price = new NewReleasePrice(); break; default: throw new IllegalArgumentException("Incorrect price code"); } } public double getCharge(int dayRented) { return price.getCharge(dayRented); } public int getFrequentRenterPoints(int dayRented) { if ((getPriceCode() == Movie.NEW_RELEASE) && dayRented > 1) { return 2; } return 1;//不喜欢用else }}
12 继续修改晚上其他几个函数
public abstract class Price { abstract int getPriceCode(); abstract double getCharge(int dayRenter); public int getFrequentRenterPoints(int dayRented) { return 1; }}class ChildrensPrice extends Price { @Override int getPriceCode() { return Movie.CHILDREN; } @Override double getCharge(int dayRented) { double result = 1.5; if (dayRented > 3) { result += (dayRented - 3) * 1.5; } return result; }}class NewReleasePrice extends Price { @Override int getPriceCode() { return Movie.NEW_RELEASE; } @Override double getCharge(int dayRented) { return dayRented * 3; } public int getFrequentRenterPoints(int dayRented) { return (dayRented > 1) ? 2 : 1; }}class RegularPrice extends Price { @Override int getPriceCode() { return Movie.REGULAR; } @Override double getCharge(int dayRented) { double result = 2; if (dayRented > 2) { result += (dayRented - 2) * 1.5; } return result; }}import org.junit.Before;public class Movie { public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; public static final int CHILDREN = 2; private String title; private int priceCode; private Price price; public Movie(String title, int priceCode) { this.title = title; setPriceCode(priceCode); } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public int getPriceCode() { return price.getPriceCode(); } public void setPriceCode(int priceCode) { switch (priceCode) { case REGULAR: price = new RegularPrice(); break; case CHILDREN: price = new ChildrensPrice(); break; case NEW_RELEASE: price = new NewReleasePrice(); break; default: throw new IllegalArgumentException("Incorrect price code"); } } public double getCharge(int dayRented) { return price.getCharge(dayRented); } public int getFrequentRenterPoints(int dayRented) { return price.getFrequentRenterPoints(dayRented); }}
0 0
- 重构 第1章 重构,第一个案例
- 重构:第一个案例
- 重构--第一个案例
- 重构,开始第一个案例
- 第一章 重构第一个案例
- 第一章 重构,第一个案例
- 重构,第一个案例(三)
- 重构第一章 重构的第一个案例
- 第一章:重构,第一个案例(读书笔记系列1)
- 《重构--改善既有代码的设计》--重构,第一个案例(1)
- 一、重构,第一案例
- 第一章:重构,第一个案例(读书笔记系列2)
- 第一章:重构,第一个案例(读书笔记系列3)
- 重构-改善既有代码的设计——第一章 重构,第一个案例
- 重构—改善既有代码的设计001:重构,第一个案例
- 重构改善既有代码的设计--重构第一个案例
- 《重构-改善代码既有的设计》重构,第一个案例
- 代码重构案例
- Android笔记:登录显示与隐藏密码
- 第八周拓展实践1小明借书
- adb server version (31) doesn't match this client (36);解决
- iOS简单易用的下载上传速度(网速)监听工具
- QT下使用boost::threadpool编译问题
- 重构 第1章 重构,第一个案例
- 死锁产生的原因及避免
- leetcode oj Sum of Left Leaves
- JQuery之简单表单验证
- Python2 爬虫(四) -- 模拟登陆(人人网和知乎)
- 初识SVM(验证码识别)
- 京东的核心业务
- self以及"_"的使用-徐明伟
- iOS自动布局和UITableViewCell