diff --git a/notes/代码可读性.md b/notes/代码可读性.md index 4ba087cb..0be5c17d 100644 --- a/notes/代码可读性.md +++ b/notes/代码可读性.md @@ -3,7 +3,7 @@ * [二、用名字表达代码含义](#二用名字表达代码含义) * [三、名字不能带来歧义](#三名字不能带来歧义) * [四、良好的代码风格](#四良好的代码风格) -* [五、编写注释](#五编写注释) +* [五、为何编写注释](#五为何编写注释) * [六、如何编写注释](#六如何编写注释) * [七、提高控制流的可读性](#七提高控制流的可读性) * [八、拆分长表达式](#八拆分长表达式) @@ -43,12 +43,14 @@ 起完名字要思考一下别人会对这个名字有何解读,会不会误解了原本想表达的含义。 -用 min、max 表示数量范围;用 first、last 表示访问空间的包含范围,begin、end 表示访问空间的排除范围,即 end 不包含尾部。 +布尔相关的命名加上 is、can、should、has 等前缀。 + +- 用 min、max 表示数量范围; +- 用 first、last 表示访问空间的包含范围; +- begin、end 表示访问空间的排除范围,即 end 不包含尾部。

-布尔相关的命名加上 is、can、should、has 等前缀。 - # 四、良好的代码风格 适当的空行和缩进。 @@ -61,11 +63,9 @@ int b = 11; // 注释 int c = 111; // 注释 ``` -语句顺序不能随意,比如与 html 表单相关联的变量的赋值应该和表单在 html 中的顺序一致; +语句顺序不能随意,比如与 html 表单相关联的变量的赋值应该和表单在 html 中的顺序一致。 -把相关的代码按块组织起来放在一起。 - -# 五、编写注释 +# 五、为何编写注释 阅读代码首先会注意到注释,如果注释没太大作用,那么就会浪费代码阅读的时间。那些能直接看出含义的代码不需要写注释,特别是并不需要为每个方法都加上注释,比如那些简单的 getter 和 setter 方法,为这些方法写注释反而让代码可读性更差。 @@ -109,14 +109,6 @@ int add(int x, int y) { } ``` -在很复杂的函数调用中对每个参数标上名字: - -```java -int a = 1; -int b = 2; -int num = add(\* x = *\ a, \* y = *\ b); -``` - 使用专业名词来缩短概念上的解释,比如用设计模式名来说明代码。 # 七、提高控制流的可读性 @@ -128,16 +120,6 @@ if (len < 10) if (10 > len) ``` -if / else 条件语句,逻辑的处理顺序为:① 正逻辑;② 关键逻辑;③ 简单逻辑。 - -```java -if (a == b) { - // 正逻辑 -} else{ - // 反逻辑 -} -``` - 只有在逻辑简单的情况下使用 ? : 三目运算符来使代码更紧凑,否则应该拆分成 if / else; do / while 的条件放在后面,不够简单明了,并且会有一些迷惑的地方,最好使用 while 来代替。 diff --git a/notes/重构.md b/notes/重构.md index 947a5455..9a3242ba 100644 --- a/notes/重构.md +++ b/notes/重构.md @@ -123,24 +123,22 @@ 影片出租店应用程序,需要计算每位顾客的消费金额。 -包括三个类:Movie、Rental 和 Customer,Rental 包含租赁的 Movie 以及天数。 +包括三个类:Movie、Rental 和 Customer。

-最开始的实现是把所有的计费代码都放在 Customer 类中。 - -可以发现,该代码没有使用 Customer 类中的任何信息,更多的是使用 Rental 类的信息,因此第一个可以重构的点就是把具体计费的代码移到 Rental 类中,然后 Customer 类的 getTotalCharge() 方法只需要调用 Rental 类中的计费方法即可。 +最开始的实现是把所有的计费代码都放在 Customer 类中。可以发现,该代码没有使用 Customer 类中的任何信息,更多的是使用 Rental 类的信息,因此第一个可以重构的点就是把具体计费的代码移到 Rental 类中,然后 Customer 类的 getTotalCharge() 方法只需要调用 Rental 类中的计费方法即可。 ```java -public class Customer { +class Customer { private List rentals = new ArrayList<>(); - public void addRental(Rental rental) { + void addRental(Rental rental) { rentals.add(rental); } - public double getTotalCharge() { + double getTotalCharge() { double totalCharge = 0.0; for (Rental rental : rentals) { switch (rental.getMovie().getMovieType()) { @@ -151,7 +149,6 @@ public class Customer { totalCharge += rental.getDaysRented() * 2; break; case Movie.Type3: - totalCharge += 1.5; totalCharge += rental.getDaysRented() * 3; break; } @@ -159,42 +156,41 @@ public class Customer { return totalCharge; } } - ``` ```java -public class Rental { +class Rental { private int daysRented; private Movie movie; - public Rental(int daysRented, Movie movie) { + Rental(int daysRented, Movie movie) { this.daysRented = daysRented; this.movie = movie; } - public Movie getMovie() { + Movie getMovie() { return movie; } - public int getDaysRented() { + int getDaysRented() { return daysRented; } } ``` ```java -public class Movie { +class Movie { - public static final int Type1 = 0, Type2 = 1, Type3 = 2; + static final int Type1 = 0, Type2 = 1, Type3 = 2; private int type; - public Movie(int type) { + Movie(int type) { this.type = type; } - public int getMovieType() { + int getMovieType() { return type; } } @@ -223,9 +219,9 @@ public class App {

-但是我们需要允许一部影片可以在运行过程中改变其所属的分类,但是上述的继承方案却不可行,因为一个对象所属的类在编译过程就确定了。 +有一条设计原则指示应该多用组合少用继承,这是因为组合比继承具有更高的灵活性。例如上面的继承方案,一部电影要改变它的计费方式,就要改变它所属的类,但是对象所属的类在编译时期就确定了,无法在运行过程中改变。(运行时多态可以在运行过程中改变一个父类引用指向的子类对象,但是无法改变一个对象所属的类。) -为了解决上述的问题,需要使用策略模式。引入 Price 类,它有多种实现。Movie 组合了一个 Price 对象,并且在运行时可以改变组合的 Price 对象,从而使得它的计费方式发生改变。 +策略模式就是使用组合替代继承的一种解决方案。引入 Price 类,它有多种实现。Movie 组合了一个 Price 对象,并且在运行时可以改变组合的 Price 对象,从而使得它的计费方式发生改变。

@@ -235,6 +231,107 @@ public class App {

+重构后的代码: + +```java +class Customer { + private List rentals = new ArrayList<>(); + + void addRental(Rental rental) { + rentals.add(rental); + } + + double getTotalCharge() { + double totalCharge = 0.0; + for (Rental rental : rentals) { + totalCharge += rental.getCharge(); + } + return totalCharge; + } +} +``` + +```java +class Rental { + private int daysRented; + + private Movie movie; + + Rental(int daysRented, Movie movie) { + this.daysRented = daysRented; + this.movie = movie; + } + + double getCharge() { + return daysRented * movie.getCharge(); + } +} +``` + +```java +interface Price { + double getCharge(); +} +``` + +```java +class Price1 implements Price { + @Override + public double getCharge() { + return 1; + } +} +``` + +```java +class Price2 implements Price { + @Override + public double getCharge() { + return 2; + } +} +``` + +```java +package imp2; + +class Price3 implements Price { + @Override + public double getCharge() { + return 3; + } +} +``` + +```java +class Movie { + + private Price price; + + Movie(Price price) { + this.price = price; + } + + double getCharge() { + return price.getCharge(); + } +} +``` + +```java +class App { + + public static void main(String[] args) { + Customer customer = new Customer(); + Rental rental1 = new Rental(1, new Movie(new Price1())); + Rental rental2 = new Rental(2, new Movie(new Price2())); + customer.addRental(rental1); + customer.addRental(rental2); + System.out.println(customer.getTotalCharge()); + } +} +``` + # 二、重构原则 ## 定义 @@ -265,9 +362,7 @@ public class App { ## 修改接口 -如果重构手法改变了已发布的接口,就必须维护新旧两个接口。 - -可以保留旧接口,让旧接口去调用新接口,并且使用 Java 提供的 @deprecation 将旧接口标记为弃用。 +如果重构手法改变了已发布的接口,就必须维护新旧两个接口。可以保留旧接口,让旧接口去调用新接口,并且使用 Java 提供的 @deprecation 将旧接口标记为弃用。 可见修改接口特别麻烦,因此除非真有必要,否则不要发布接口,并且不要过早发布接口。 @@ -467,7 +562,7 @@ Extract Method 会把很多参数和临时变量都当做参数,可以用 Repl Java 可以使用 Junit 进行单元测试。 -测试应该能够完全自动化,并能检查测试的结果。Junit 可以做到。 +测试应该能够完全自动化,并能检查测试的结果。 小步修改,频繁测试。