校园门户网站建设方案,网站做淘宝客有什么要求,爱网站在线观看视频,wordpress手机号码登录密码前言什么样的代码是好代码呢#xff1f;好的代码应该命名规范、可读性强、扩展性强、健壮性......而不好的代码又有哪些典型特征呢#xff1f;这25种代码坏味道大家要注意啦1. Duplicated Code #xff08;重复代码#xff09;重复代码就是不同地点#xff0c;有着相同的程… 前言什么样的代码是好代码呢好的代码应该命名规范、可读性强、扩展性强、健壮性......而不好的代码又有哪些典型特征呢这25种代码坏味道大家要注意啦1. Duplicated Code 重复代码重复代码就是不同地点有着相同的程序结构。一般是因为需求迭代比较快开发小伙伴担心影响已有功能就复制粘贴造成的。重复代码很难维护的如果你要修改其中一段的代码逻辑就需要修改多次很可能出现遗漏的情况。如何优化重复代码呢分三种情况讨论同一个类的两个函数含有相同的表达式class A {public void method1() {doSomething1doSomething2doSomething3}public void method2() {doSomething1doSomething2doSomething4}
}
优化手段可以使用Extract Method(提取公共函数) 抽出重复的代码逻辑组成一个公用的方法。class A {public void method1() {commonMethod();doSomething3}public void method2() {commonMethod();doSomething4}public void commonMethod(){doSomething1doSomething2}
}
两个互为兄弟的子类内含相同的表达式class A extend C {public void method1() {doSomething1doSomething2doSomething3}
}class B extend C {public void method1() {doSomething1doSomething2doSomething4}
}优化手段对两个类都使用Extract Method(提取公共函数)然后把抽取出来的函数放到父类中。class C {public void commonMethod(){doSomething1doSomething2}
}
class A extend C {public void method1() {commonMethod();doSomething3}
}class B extend C {public void method1() {commonMethod();doSomething4}
}
两个毫不相关的类出现重复代码如果是两个毫不相关的类出现重复代码可以使用Extract Class将重复代码提炼到一个类中。这个新类可以是一个普通类也可以是一个工具类看具体业务怎么划分吧。2 .Long Method (长函数)长函数是指一个函数方法几百行甚至上千行可读性大大降低不便于理解。反例如下public class Test {private String name;private VectorOrder orders new VectorOrder();public void printOwing() {//print bannerSystem.out.println(****************);System.out.println(*****customer Owes *****);System.out.println(****************);//calculate totalAmountEnumeration env orders.elements();double totalAmount 0.0;while (env.hasMoreElements()) {Order order (Order) env.nextElement();totalAmount order.getAmout();}//print detailsSystem.out.println(name: name);System.out.println(amount: totalAmount);......}
}
可以使用Extract Method抽取功能单一的代码段组成命名清晰的小函数去解决长函数问题正例如下:public class Test {private String name;private VectorOrder orders new VectorOrder();public void printOwing() {//print bannerprintBanner();//calculate totalAmountdouble totalAmount getTotalAmount();//print detailsprintDetail(totalAmount);}void printBanner(){System.out.println(****************);System.out.println(*****customer Owes *****);System.out.println(****************);}double getTotalAmount(){Enumeration env orders.elements();double totalAmount 0.0;while (env.hasMoreElements()) {Order order (Order) env.nextElement();totalAmount order.getAmout();}return totalAmount;}void printDetail(double totalAmount){System.out.println(name: name);System.out.println(amount: totalAmount);}}
3. Large Class (过大的类)一个类做太多事情维护了太多功能可读性变差性能也会下降。举个例子订单相关的功能你放到一个类A里面商品库存相关的也放在类A里面积分相关的还放在类A里面...反例如下Class A{public void printOrder(){System.out.println(订单);}public void printGoods(){System.out.println(商品);}public void printPoints(){System.out.println(积分);}
}试想一下乱七八糟的代码块都往一个类里面塞还谈啥可读性。应该按单一职责使用Extract Class把代码划分开正例如下Class Order{public void printOrder(){System.out.println(订单);}
}Class Goods{public void printGoods(){System.out.println(商品);}
}Class Points{ public void printPoints(){System.out.println(积分);}}
}4. Long Parameter List (过长参数列)方法参数数量过多的话可读性很差。如果有多个重载方法参数很多的话有时候你都不知道调哪个呢。并且如果参数很多做新老接口兼容处理也比较麻烦。public void getUserInfoString name,String age,String sex,String mobile){// do something ...
}
如何解决过长参数列问题呢将参数封装成结构或者类比如我们将参数封装成一个DTO类如下public void getUserInfoUserInfoParamDTO userInfoParamDTO){// do something ...
}class UserInfoParamDTO{private String name;private String age; private String sex;private String mobile;
}
5. Divergent Change 发散式变化对程序进行维护时, 如果添加修改组件, 要同时修改一个类中的多个方法, 那么这就是 Divergent Change。举个汽车的例子某个汽车厂商生产三种品牌的汽车BMW、Benz和LaoSiLaiSi每种品牌又可以选择燃油、纯电和混合动力。反例如下/*** 公众号捡田螺的小男孩*/
public class Car {private String name;void start(Engine engine) {if (HybridEngine.equals(engine.getName())) {System.out.println(Start Hybrid Engine...);} else if (GasolineEngine.equals(engine.getName())) {System.out.println(Start Gasoline Engine...);} else if (ElectricEngine.equals(engine.getName())) {System.out.println(Start Electric Engine);}}void drive(Engine engine,Car car) {this.start(engine);System.out.println(Drive getBrand(car) car...);}String getBrand(Car car) {if (Baoma.equals(car.getName())) {return BMW;} else if (BenChi.equals(car.getName())) {return Benz;} else if (LaoSiLaiSi.equals(car.getName())) {return LaoSiLaiSi;}return null;}}
如果新增一种品牌新能源电车然后它的启动引擎是核动力呢那么就需要修改Car类的start和getBrand方法啦这就是代码坏味道Divergent Change 发散式变化。如何优化呢一句话总结拆分类将总是一起变化的东西放到一块。★运用提炼类(Extract Class) 拆分类的行为。如果不同的类有相同的行为提炼超类(Extract Superclass) 和 提炼子类(Extract Subclass)。”正例如下因为Engine是独立变化的所以提取一个Engine接口如果新加一个启动引擎多一个实现类即可。如下//IEngine
public interface IEngine {void start();
}public class HybridEngineImpl implements IEngine { Overridepublic void start() {System.out.println(Start Hybrid Engine...);}
}
因为drive方法依赖于CarIEnginegetBand方法;getBand方法是变化的也跟Car是有关联的所以可以搞个抽象Car的类每个品牌汽车继承于它即可如下public abstract class AbstractCar {protected IEngine engine;public AbstractCar(IEngine engine) {this.engine engine;}public abstract void drive();
}//奔驰汽车
public class BenzCar extends AbstractCar {public BenzCar(IEngine engine) {super(engine);}Overridepublic void drive() {this.engine.start();System.out.println(Drive getBrand() car...);}private String getBrand() {return Benz;}
}//宝马汽车
public class BaoMaCar extends AbstractCar {public BaoMaCar(IEngine engine) {super(engine);}Overridepublic void drive() {this.engine.start();System.out.println(Drive getBrand() car...);}private String getBrand() {return BMW;}
}细心的小伙伴可以发现不同子类BaoMaCar和BenzCar的drive方法还是有相同代码所以我们可以再扩展一个抽象子类把drive方法推进去如下:public abstract class AbstractRefinedCar extends AbstractCar {public AbstractRefinedCar(IEngine engine) {super(engine);}Overridepublic void drive() {this.engine.start();System.out.println(Drive getBrand() car...);}abstract String getBrand();
}//宝马
public class BaoMaRefinedCar extends AbstractRefinedCar {public BaoMaRefinedCar(IEngine engine) {super(engine);}OverrideString getBrand() {return BMW;}
}如果再添加一个新品牌搞个子类继承AbstractRefinedCar即可如果新增一种启动引擎也是搞个类实现IEngine接口即可6. Shotgun Surgery散弹式修改当你实现某个小功能时你需要在很多不同的类做出小修改。这就是Shotgun Surgery散弹式修改。它跟发散式变化(Divergent Change) 的区别就是它指的是同时对多个类进行单一的修改发散式变化指在一个类中修改多处。反例如下public class DbAUtils {Value(${db.mysql.url})private String mysqlDbUrl;...
}public class DbBUtils {Value(${db.mysql.url})private String mysqlDbUrl;...
}
多个类使用了db.mysql.url这个变量如果将来需要切换mysql到别的数据库如Oracle那就需要修改多个类的这个变量如何优化呢将各个修改点集中到一起抽象成一个新类。★可以使用 Move Method 搬移函数和 Move Field 搬移字段把所有需要修改的代码放进同一个类如果没有合适的类就去new一个。”正例如下public class DbUtils {Value(${db.mysql.url})private String mysqlDbUrl;...
}
7. Feature Envy (依恋情节)某个函数为了计算某个值从另一个对象那里调用几乎半打的取值函数。通俗点讲就是一个函数使用了大量其他类的成员有人称之为红杏出墙的函数。反例如下public class User{private Phone phone;public User(Phone phone){this.phone phone;}public void getFullPhoneNumber(Phone phone){System.out.println(areaCode: phone.getAreaCode());System.out.println(prefix phone.getPrefix());System.out.println(number phone.getNumber());}
}
如何解决呢在这种情况下你可以考虑将这个方法移动到它使用的那个类中。例如要将 getFullPhoneNumber()从 User 类移动到Phone类中因为它调用了Phone类的很多方法。8. Data Clumps数据泥团数据项就像小孩子喜欢成群结队地呆在一块。如果一些数据项总是一起出现的并且一起出现更有意义的就可以考虑按数据的业务含义来封装成数据对象。反例如下public class User {private String firstName;private String lastName;private String province;private String city;private String area;private String street;
}正例public class User {private UserName username;private Adress adress;
}class UserName{private String firstName;private String lastName;
}
class Address{private String province;private String city;private String area;private String street;
}
9. Primitive Obsession 基本类型偏执)多数编程环境都有两种数据类型结构类型和基本类型。这里的基本类型如果指Java语言的话不仅仅包括那八大基本类型哈也包括String等。如果是经常一起出现的基本类型可以考虑把它们封装成对象。我个人觉得它有点像Data Clumps数据泥团 举个反例如下// 订单
public class Order {private String customName;private String address;private Integer orderId;private Integer price;
}
正例// 订单类
public class Order {private Custom custom;private Integer orderId;private Integer price;
}
// 把custom相关字段封装起来在Order中引用Custom对象
public class Custom {private String name;private String address;
}当然这里不是所有的基本类型都建议封装成对象有关联或者一起出现的才这么建议哈。10. Switch Statements Switch 语句这里的Switch语句不仅包括Switch相关的语句也包括多层if...else的语句哈。很多时候switch语句的问题就在于重复如果你为它添加一个新的case语句就必须找到所有的switch语句并且修改它们。示例代码如下 String medalType guest;if (guest.equals(medalType)) {System.out.println(嘉宾勋章);} else if (vip.equals(medalType)) {System.out.println(会员勋章);} else if (guard.equals(medalType)) {System.out.println(守护勋章);}...
这种场景可以考虑使用多态优化//勋章接口
public interface IMedalService {void showMedal();
}//守护勋章策略实现类
public class GuardMedalServiceImpl implements IMedalService {Overridepublic void showMedal() {System.out.println(展示守护勋章);}
}
//嘉宾勋章策略实现类
public class GuestMedalServiceImpl implements IMedalService {Overridepublic void showMedal() {System.out.println(嘉宾勋章);}
}//勋章服务工厂类
public class MedalServicesFactory {private static final MapString, IMedalService map new HashMap();static {map.put(guard, new GuardMedalServiceImpl());map.put(vip, new VipMedalServiceImpl());map.put(guest, new GuestMedalServiceImpl());}public static IMedalService getMedalService(String medalType) {return map.get(medalType);}
}当然多态只是优化的一个方案一个方向。如果只是单一函数有些简单选择示例并不建议动不动就使用动态因为显得有点杀鸡使用牛刀了。11.Parallel Inheritance Hierarchies 平行继承体系平行继承体系 其实算是Shotgun Surgery的特殊情况啦。当你为A类的一个子类Ax也必须为另一个类B相应的增加一个子类Bx。解决方法遇到这种情况就要消除两个继承体系之间的引用有一个类是可以去掉继承关系的。12. Lazy Class (冗赘类)把这些不再重要的类里面的逻辑合并到相关类删掉旧的。一个比较常见的场景就是假设系统已经有日期工具类DateUtils有些小伙伴在开发中需要用到日期转化等不管三七二十一又自己实现一个新的日期工具类。13. Speculative Generality(夸夸其谈未来性)尽量避免过度设计的代码。例如只有一个if else那就不需要班门弄斧使用多态;如果某个抽象类没有什么太大的作用就运用Collapse Hierarchy折叠继承体系如果函数的某些参数没用上就移除。14. Temporary Field(令人迷惑的临时字段)某个实例变量仅为某种特定情况而定而设这样的代码就让人不易理解我们称之为 Temporary Field(令人迷惑的临时字段)。反例如下:public class PhoneAccount {private double excessMinutesCharge;private static final double RATE 8.0;public double computeBill(int minutesUsed, int includedMinutes) {excessMinutesCharge 0.0;int excessMinutes minutesUsed - includedMinutes;if (excessMinutes 1) {excessMinutesCharge excessMinutes * RATE;}return excessMinutesCharge;}public double chargeForExcessMinutes(int minutesUsed, int includedMinutes) {computeBill(minutesUsed, includedMinutes);return excessMinutesCharge;}
}思考一下临时字段excessMinutesCharge是否多余呢15. Message Chains (过度耦合的消息链)当你看到用户向一个对象请求另一个对象然后再向后者请求另一个对象然后再请求另一个对象...这就是消息链。实际代码中你看到的可能是一长串getThis或一长串临时变量。反例如下A.getB().getC().getD().getTianLuoBoy().getData();
A想要获取需要的数据时必须要知道B又必须知道C,又必须知道D...其实A需要知道得太多啦回头想下封装性嘻嘻。其实可以通过拆函数或者移动函数解决比如由B作为代理搞个函数直接返回A需要数据。16. Middle Man 中间人对象的基本特征之一就是封装即对外部世界隐藏其内部细节。封装往往伴随委托过度运用委托就不好某个类接口有一半的函数都委托给其他类。可以使用Remove Middle Man优化。反例如下A.B.getC(){return C.getC();
}
其实A可以直接通过C去获取C而不需要通过B去获取。17. Inappropriate Intimacy狎昵关系如果两个类过于亲密过分狎昵你中有我我中有你两个类彼此使用对方的私有的东西就是一种坏代码味道。我们称之为Inappropriate Intimacy狎昵关系建议尽量把有关联的方法或属性抽离出来放到公共类以减少关联。18. Alternative Classes with Different Interfaces 异曲同工的类A类的接口a和B类的接口b做的的是相同一件事或者类似的事情。我们就把A和B叫做异曲同工的类。可以通过重命名移动函数或抽象子类等方式优化19. Incomplete Library Class (不完美的类库)大多数对象只要够用就好如果类库构造得不够好我们不可能修改其中的类使它完成我们希望完成的工作。可以酱紫包一层函数或包成新的类。20. Data Class 纯数据类什么是Data Class? 它们拥有一些字段以及用于访问(读写)这些字段的函数。这些类很简单仅有公共成员变量或简单操作的函数。如何优化呢将相关操作封装进去减少public成员变量。比如如果拥有public字段- Encapsulate Field如果这些类内含容器类的字段应该检查它们是不是得到了恰当地封装- Encapsulate Collection封装起来对于不该被其他类修改的字段- Remove Setting Method-找出取值/设置函数被其他类运用的地点- Move Method 把这些调用行为搬移到Data Class来。如果无法搬移整个函数就运用Extract Method产生一个可被搬移的函数-Hide Method把这些取值/设置函数隐藏起来。21. Refused Bequest 被拒绝的馈赠子类应该继承父类的数据和函数。子类继承得到所有函数和数据却只使用了几个那就是继承体系设计错误需要优化。需要为这个子类新建一个兄弟类-Push Down Method和Push Down Field把所有用不到的函数下推给兄弟类这样一来超类就只持有所有子类共享的东西。所有超类都应该是抽象的。如果子类复用了超类的实现又不愿意支持超类的接口可以不以为然。但是不能胡乱修改继承体系-Replace Inheritance with Delegation(用委派替换继承).22. Comments (过多的注释)这个点不是说代码不建议写注释哦而是建议大家避免用注释解释代码避免过多的注释。这些都是常见注释的坏味道多余的解释日志式注释用注释解释变量等...如何优化呢方法函数、变量的命名要规范、浅显易懂、避免用注释解释代码。关键、复杂的业务使用清晰、简明的注释23. 神奇命名方法函数、变量、类名、模块等都需要简单明了浅显易懂。避免靠自己主观意识瞎起名字。反例boolean test chenkParamResult(req);
正例boolean isParamPass chenkParamResult(req);
24. 神奇魔法数日常开发中经常会遇到这种代码if(userType1){//doSth1
}else If( userType 2){//doSth2
}
...
代码中的这个1和2都表示什么意思呢再比如setStatus(1)中的1又表示什么意思呢看到类似坏代码可以这两种方式优化新建个常量类把一些常量放进去统一管理并且写好注释建一个枚举类把相关的魔法数字放到一起管理。25. 混乱的代码层次调用我们代码一般会分dao层、service层和controller层。dao层主要做数据持久层的工作与数据库打交道。service层主要负责业务逻辑处理。controller层负责具体的业务模块流程的控制。所以一般就是controller调用serviceservice调dao。如果你在代码看到controller直接调用dao那可以考虑是否优化啦。反例如下RestController(user)
public class UserController {Autowiredprivate UserDao userDao;RequestMapping(/queryUserInfo)public String queryUserInfo(String userName) {return userDao.selectByUserName(userName);}
}参考与感谢软工实验常见的代码坏味道以及重构举例[2]22种代码的坏味道一句话概括[3]【重构】 代码的坏味道总结 Bad Smell[4]Code Smell[5]《重构改善既有代码的设计》
往期推荐
ThreadLocal内存溢出代码演示和原因分析SimpleDateFormat线程不安全的5种解决方案2万字66道并发面试题及答案