摘要:
本文通過簡單通俗的例子
異味這個詞
這是一個CAD系統
class Shape {
final static int TYPELINE =
final static int TYPERECTANGLE =
final static int TYPECIRCLE =
int shapeType;
//線條的開始點
//長方形左下角的點
//圓心
Point p
//線條的結束點
//長方形的右上角的點
//如果是圓的話
Point p
int radius;
}
class CADApp {
void drawShapes(Graphics graphics
for (int i =
switch (shapes[i]
case Shape
graphics
break;
case Shape
//畫四條邊
graphics
graphics
graphics
graphics
break;
case Shape
graphics
break;
}
}
}
}
代碼都是一直在改變的
現在我們有一個問題: 如果我們需要支持更多的形狀(比如三角形)
好
class Shape {
final static int TYPELINE =
final static int TYPERECTANGLE =
final static int TYPECIRCLE =
final static int TYPETRIANGLE =
int shapeType;
Point p
Point p
//三角形的第三個點
Point p
int radius;
}
class CADApp {
void drawShapes(Graphics graphics
for (int i =
switch (shapes[i]
case Shape
graphics
break;
case Shape
//畫四條邊
graphics
graphics
graphics
graphics
break;
case Shape
graphics
break;
case Shape
graphics
graphics
graphics
break;
}
}
}
}
如果以後要支持更多的形狀
理想情況下
現在的情況恰好相反!
每當我們增加新的形狀
怎麼讓代碼穩定(也就是無需修改)?這個問題是個好問題!不過老規矩
我們先看看另外一個方法
怎麼判斷代碼的穩定性?
要判斷代碼的穩定性
可惜
有個更簡單的方法
此外
現在
示例代碼中的代碼異味
第一種異味
class Shape {
final int TYPELINE =
final int TYPERECTANGLE =
final int TYPECIRCLE =
int shapeType;
}
這樣的異味
第二種異味
class Shape {
Point p
Point p
int radius; //有時候不用
}
第三種異味
class Shape {
Point p
Point p
}
第四種異味
現在
消除代碼異味
大多數情況下
(當然
class Shape {
}
class Line extends Shape {
Point startPoint;
Point endPoint;
}
class Rectangle extends Shape {
Point lowerLeftCorner;
Point upperRightCorner;
}
class Circle extends Shape {
Point center;
int radius;
}
因為現在沒有類別代碼了
class CADApp {
void drawShapes(Graphics graphics
for (int i =
if (shapes[i] instanceof Line) {
Line line = (Line)shapes[i];
graphics
} else if (shapes[i] instanceof Rectangle) {
Rectangle rect = (Rectangle)shapes[i];
graphics
graphics
graphics
graphics
} else if (shapes[i] instanceof Circle) {
Circle circle = (Circle)shapes[i];
graphics
}
}
}
}
因為沒有類別代碼了
消除代碼異味
經常地
class CADApp {
void drawShapes(Graphics graphics
for (int i =
if (shapes[i] instanceof Line) {
畫線條;
} else if (shapes[i] instanceof Rectangle) {
畫長方形;
} else if (shapes[i] instanceof Circle) {
畫圓;
}
}
}
}
條件下的代碼還是不怎麼一樣
class CADApp {
void drawShapes(Graphics graphics
for (int i =
if (shapes[i] instanceof Line) {
畫出形狀;
} else if (shapes[i] instanceof Rectangle) {
畫出形狀;
} else if (shapes[i] instanceof Circle) {
畫出形狀;
}
}
}
}
好
class CADApp {
void drawShapes(Graphics graphics
for (int i =
畫出形狀;
}
}
}
最後
class CADApp {
void drawShapes(Graphics graphics
for (int i =
shapes[i]
}
}
}
當然
abstract class Shape {
abstract void draw(Graphics graphics);
}
class Line extends Shape {
Point startPoint;
Point endPoint;
void draw(Graphics graphics) {
graphics
}
}
class Rectangle extends Shape {
Point lowerLeftCorner;
Point upperRightCorner;
void draw(Graphics graphics) {
graphics
graphics
graphics
graphics
}
}
class Circle extends Shape {
Point center;
int radius;
void draw(Graphics graphics) {
graphics
}
}
將抽象類變成接口
現在
interface Shape {
void draw(Graphics graphics);
}
class Line implements Shape {
}
class Rectangle implements Shape {
}
class Circle implements Shape {
}
改進後的代碼
改進後的代碼就像下面這樣
interface Shape {
void draw(Graphics graphics);
}
class Line implements Shape {
Point startPoint;
Point endPoint;
void draw(Graphics graphics) {
graphics
}
}
class Rectangle implements Shape {
Point lowerLeftCorner;
Point upperRightCorner;
void draw(Graphics graphics) {
graphics
graphics
graphics
graphics
}
}
class Circle implements Shape {
Point center;
int radius;
void draw(Graphics graphics) {
graphics
}
}
class CADApp {
void drawShapes(Graphics graphics
for (int i =
shapes[i]
}
}
}
如果我們想要支持更多的圖形(比如
另一個例子
讓我們來看一下另外一個例子
常規用戶必須每隔
常規用戶跟管理員可以打印報表
先看一下當前的代碼
class UserAccount {
final static int USERTYPE_NORMAL =
final static int USERTYPE_ADMIN =
final static int USERTYPE_GUEST =
int userType;
String id;
String name;
String password;
Date dateOfLastPasswdChange;
public boolean checkPassword(String password) {
}
}
class InventoryApp {
void login(UserAccount userLoggingIn
if (userLoggingIn
GregorianCalendar today = new GregorianCalendar();
GregorianCalendar expiryDate = getAccountExpiryDate(userLoggingIn);
if (today
//提示用戶修改密碼
}
}
}
GregorianCalendar getAccountExpiryDate(UserAccount account) {
int passwordMaxAgeInDays = getPasswordMaxAgeInDays(account);
GregorianCalendar expiryDate = new GregorianCalendar();
expiryDate
expiryDate
return expiryDate;
}
int getPasswordMaxAgeInDays(UserAccount account) {
switch (account
case UserAccount
return
case UserAccount
return
case UserAccount
return Integer
}
}
void printReport(UserAccount currentUser) {
boolean canPrint;
switch (currentUser
case UserAccount
canPrint = true;
break;
case UserAccount
canPrint = true;
break;
case UserAccount
canPrint = false;
}
if (!canPrint) {
throw new SecurityException(
}
//打印報表
}
}
用一個對象代替一種類別(注意
根據之前講的解決方法
abstract class UserAccount {
String id;
String name;
String password;
Date dateOfLastPasswdChange;
abstract int getPasswordMaxAgeInDays();
abstract boolean canPrintReport();
}
class NormalUserAccount extends UserAccount {
int getPasswordMaxAgeInDays() {
return
}
boolean canPrintReport() {
return true;
}
}
class AdminUserAccount extends UserAccount {
int getPasswordMaxAgeInDays() {
return
}
boolean canPrintReport() {
return true;
}
}
class GuestUserAccount extends UserAccount {
int getPasswordMaxAgeInDays() {
return Integer
}
boolean canPrintReport() {
return false;
}
}
但問題是
class UserAccount {
UserType userType;
String id;
String name;
String password;
Date dateOfLastPasswdChange;
UserType getType() {
return userType;
}
}
class UserType {
int passwordMaxAgeInDays;
boolean allowedToPrintReport;
UserType(int passwordMaxAgeInDays
this
this
}
int getPasswordMaxAgeInDays() {
return passwordMaxAgeInDays;
}
boolean canPrintReport() {
return allowedToPrintReport;
}
static UserType normalUserType = new UserType(
static UserType adminUserType = new UserType(
static UserType guestUserType = new UserType(Integer
}
class InventoryApp {
void login(UserAccount userLoggingIn
if (userLoggingIn
GregorianCalendar today = new GregorianCalendar();
GregorianCalendar expiryDate = getAccountExpiryDate(userLoggingIn);
if (today
//提示用戶修改密碼
}
}
}
GregorianCalendar getAccountExpiryDate(UserAccount account) {
int passwordMaxAgeInDays = getPasswordMaxAgeInDays(account);
GregorianCalendar expiryDate = new GregorianCalendar();
expiryDate
expiryDate
return expiryDate;
}
int getPasswordMaxAgeInDays(UserAccount account) {
return account
}
void printReport(UserAccount currentUser) {
boolean canPrint;
canPrint = currentUser
if (!canPrint) {
throw new SecurityException(
}
//打印報表
}
}
注意到了吧
總結一下類別代碼的移除
要移動一些類別代碼和switch表達式
當不同的類別具有比較多不同的行為時
普遍的代碼異味
類別代碼和switch表達式是比較普遍的代碼異味
下面是大概的異味列表
代碼重復
太多的注釋
類別代碼(type code)
switch或者一大串if
想給一個變量
用類似XXXUtil
在變量
一些實例中的變量有時有用
一個方法的代碼太多
一個類的代碼太多
一個方法有太多參數
兩個類都引用了彼此(依賴於彼此)
From:http://tw.wingwit.com/Article/program/Java/hx/201311/27159.html