领域驱动设计在马蜂窝优惠中心重构中的实践

前言正如领域驱动设计之父 Eric Evans 所著一书的书名所述,领域驱动设计(Domain Driven Design)是一种软件核心复杂性应对之道。 在我们解决现实业务问题时,会面对非常复杂的业务逻辑。即使是同一个事物,在多个子业务单元下代表的意思也是不完全一样的。比如「商品」这个词,在商品详情页语境中,是指「商品基本信息」;在下单页语境中,是指「购买项」;而在物流页面语境中,又变成了「被运送的货物」。 DDD 的核心思想就是让正确的领域模型发挥作用。所谓「术业有专攻」,DDD 指导软件开发人员将不同的子业务单元划分为不同的子领域,在各个子领域内部分别对事物进行建模,来应对业务的复杂性。 一、重构优惠中心的背景我们在实际的开发过程中都遇到过这种情况,最初因为业务逻辑比较单一,为了快速实现功能, 以及对成本、风险等因素的综合考虑,我们会为业务统一创建一个大的模型,各个模块都使用这同一个模型。但随着业务的发展,各子领域的逻辑越来越复杂,对这个大模型的修改就会变成一种灾难,有时明明是要改一个 A 子领域的逻辑,却莫名其妙影响到了 B 或者 C 子领域的线上功能。 优惠中心就是一个例子。优惠中心主要负责马蜂窝各业务线商品的优惠活动管理,以及计算不同用户的优惠结果。「商品管理」和「优惠管理」作为两个不同的业务单元,在初期被设计为共用一个商品模型,由商品模块统一管理。 <center>图1 :初期商品模型</center> 出现的问题随着业务的发展,优惠的形式不断推陈出新,业务形态逐渐多样,业务方的需求也越来越个性化,导致后期的优惠中心无论从功能上还是系统上都出现了一些具体的问题: 1. 功能上来说,不够灵活 优惠信息是作为商品信息的一个属性在商品管理模块配置的。比如为了引导用户使用 App 需要设置 A 类型优惠,就通过在商品信息的编辑页面增加一个 A 类型优惠配置项实现;如果某个商品的 A 类型优惠需要在 0:00 分生效,业务同学就必须在电脑前等到 0:00 更新商品信息来上线优惠活动。 另外,如果想要创建针对所有商品都适用的优惠,按照之前的模式,所有的商品都要设置一遍,这几乎是不可接受的。 2. 从系统层面看,不易扩展 优惠信息存储在商品信息中,优惠信息是通过商品管理模块的接口输出的。如果要新增一种优惠类型,商品信息相关的表就要增加字段,商品的表会越来越大;如果要迭代一个优惠的逻辑,就有可能影响到商品管理模块的功能。 3. 不利于迭代 由于优惠信息仅仅作为商品的一个属性,没有自己的生命周期,所以很难去统计某一次设置的优惠的投入产出比,从而指导后续的功能优化。 重构优惠中心的预期系统层面上,要把优惠相关的业务逻辑独立出来,单独设计和实现;应用层面上,优惠中心会有自己的独立后台,负责管理优惠活动;也会有独立的优惠计算接口,负责 C 端用户使用优惠时的计算。二、分什么选择 DDD避免贫血模型基于传统的 MVC 架构开发功能的时候,Model 层本质上是一个 DAO 层,业务逻辑通常会封装在 Service 层,然后 Controller 通过调用 Service 层来完成对外的功能。这种模式下,数据和行为分别被割裂到了 Model 和 Service 两层。我们把这种只承载数据,但没有业务行为的 Model 称为「贫血模型」。 ...

July 12, 2019 · 1 min · jiezi

重构你可能不知道的重构场景

什么是重构?“重构”一词想必你已经听腻了,就是整理代码呗,不不不,重构旨在不改变调用者行为的前提下,对内部逻辑进行调整优化,提高其理解性,降低其修改成本,它是一门艺术,是程序员至高无上的荣耀…… 何时重构?怎么重构?经常听到周边的人抱怨没有时间重构,重构并不是单独抽出时间集中处理的,而是当你想要做某个功能时,随手把需要重构的地方安排了。 逻辑重复重复代码是最核心常见的预警信息,如果有两个及以上的重复逻辑,就应该考虑将其合并。比如同一类或不同类中的函数存在相同逻辑的部分,就应该把相同部分抽象为独立函数或类。 长函数应该有很多同学经手过别人数百行甚至上千行的代码,让人质疑人生。为方便理解,最好的方式是把长函数分解为若干小函数,搭配上易理解的函数名,便可以像自然语言一样理解代码。 参数过多有一种习惯非常不好,就是把所有要用到的变量当做函数的参数,这样会加剧代码的理解难度,拓展极其困难,当需要更多数据时,不得不修改所有函数的参数,牵一发动全身。如果把对象作为参数,需要用到的数据都放进对象里,就可以有效解决参数过长的问题。 函数出轨你要是发现一个函数频繁的调用某一个类,它很可能给你戴了绿帽子,不如忍痛割爱,放其自由吧,把函数归并到它喜欢的类,也许他们在一起生活更为合适,你一定会找到一个适合的人。 变化扩散如果新加入一个业务类型(例如支付渠道、数据库类型等)时,需要改动很多地方才能实现,这就意味着还有改进的空间,可以将引起变化的原因抽出来做为配置,并将变化的函数放置到一个类中,这样不仅可以做到修改一处就应对变化,还可以很清晰的知道哪些函数会受到影响。 工具小助手一款语言包含很多基本类型与内置函数,但不能满足所有需求,比如金额单位转换、时间数组格式转换、UUID生成等简单又容易忽略的小功能,如果这些功能出现的频率很高,规则改变会带来一连串的修改,这时可以考虑将这些小功能抽象为工具函数,并将这些函数组合为工具类。 意淫的功能有些逻辑以为将来会有一些变化,于是安插了很多钩子函数应对非必要的特殊情况,这样往往提高了系统复杂性和理解成本,如果安插的钩子都能被用到且有价值,那么就使用,否则还是不要放在代码里阻碍视线了。 switch过多假如现在要做一个支持微信、支付宝、招行等渠道的支付平台,需要对接不同渠道,因为不同渠道对接方式不同,就需要用switch来根据类型选择对应渠道的对接方式,但是很多地方都可能用到这个switch,一旦新渠道加入就要满世界的找哪里用到了switch。 可以将switch语句移植为独立的函数,将这些函数组成基类,case语句调用子类对应的函数,具体实现让子类去完成,这样支付渠道的增加和变更只需要修改一个类即可。 多余的类创建的每一个类,对于其他人来讲都是有理解成本的,如果曾经为某个变化所添加的类,在实际场景中并没有发生变化,那么就把这个类去掉吧,我们需要真正有价值、理解成本低的系统。 让人犯晕的变量一个类会设置一些为特殊情况设置的变量,这些变量不一定都会被使用,经手你代码的人还要猜测当时设置这些变量的目的,非常让人头大,不如把这些变量和相关函数单独放在一个类中,屏蔽具体细节,需要的功能通过函数来表达,会使功能扩展更高效。 幽灵类项目中偶尔会出现一些“幽灵类”,这些类没有做什么实际工作,只是负责调用其它的类,不如把这个“中间人”去掉,让实际要调用的那个类与调用者发生关系。 雷同的类如果两个类,其中某几个函数作用相同,名称不同,那就可以通过修改名称或移植函数的方式将两个相似的类保持一致,然后把两个类抽象出基类,以便扩展。 过多的注释注释多并不是一件坏事,它是重构的领路人,当你感觉需要为某段代码写上注释时,这意味着你认为这段代码不容易被他人理解,也侧面证明了这就是重构发出的预警信号,所以当想要写注释时,就先重构,争取让注释都变得多余。 如果你喜欢本文,可以关注微信公众号“关爱程序员社区”(icoder_club),干货更不停

May 13, 2019 · 1 min · jiezi

重构代码(应如写诗)

背景最近公司做了个项目,深深体会到架构设计以及代码优化有多么的重要。回头看自己的代码都觉得特别混乱,有时候还要看很久才能看懂,可扩展性特别差,完全是为了完成需求而编码的。说得形象一点就像修水管,最后全部都漏水了。个人觉得代码重构非常有必要,写程序不但要给机器运行,更让人看的明白。写代码如写诗一样才行。实例一个图书馆出租书的程序。计算每一个读者的消费金额并且打印详情清单。打印信息:读者租了哪些书、租期多长、根据租借时间和书的类型算出费用。书分类:普通读本、少儿读本、新书计算费用,以及计算积分。积分根据书的种类是否为新书而又有所不同。常见代码按照实例需求,经常都是类似这样子写代码的,如下:Book书本类主要是关于书名称和分类信息。/** * 书本 /public class Book { public static final int CHILDRENS = 2; public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; private String title; private int priceCode; public Book() { } public Book(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; }}Rental 租借信息主要是写租借信息,包括书和租借天数的关系。/* * 租借信息 /public class Rental { private Book book; private int daysRented;//租借天数 public Rental() { } public Rental(Book book, int daysRented) { this.book = book; this.daysRented = daysRented; } public Book getBook() { return book; } public void setBook(Book book) { this.book = book; } public int getDaysRented() { return daysRented; } public void setDaysRented(int daysRented) { this.daysRented = daysRented; }}Customer 读者类主要写租借费用计算以及租借的书的关系。/* * 读者 */public class Customer { private String name; private List<Rental> rentals = new ArrayList(); public Customer() { } public Customer(String name) { this.name = name; } //添加租书信息 public void addRental(Rental rental) { rentals.add(rental); } //生成订单 public String generateOrder() { double total = 0;//计算租借总数量 int frequentRenterPoints = 0;//计算积分 String result = “Rental Record for “+getName()+"\n”; for (Rental rental : rentals) { double thisAmount = 0; switch (rental.getBook().getPriceCode()){ case Book.REGULAR: thisAmount += 2; if (rental.getDaysRented() > 2){ thisAmount += (rental.getDaysRented() - 2) *1.5; } break; case Book.NEW_RELEASE: thisAmount += rental.getDaysRented()3; break; case Book.CHILDRENS: thisAmount += 1.5; if (rental.getDaysRented() > 3){ thisAmount += (rental.getDaysRented() - 3) 1.5; } break; } frequentRenterPoints++; if ((rental.getBook().getPriceCode() == Book.NEW_RELEASE) && rental.getDaysRented() >1){ frequentRenterPoints++; } if ((rental.getBook().getPriceCode() == Book.NEW_RELEASE) && rental.getDaysRented() >1) { frequentRenterPoints++; } result += “\t”+rental.getBook().getTitle() + “\t”+String.valueOf(thisAmount)+"\n”; total +=thisAmount; } result += “Amount owed is “+ String.valueOf(total) +"\n”; result += “You earned “+ String.valueOf(frequentRenterPoints) +“frequent renter points”; return result; } public String getName() { return name; } public void setName(String name) { this.name = name; }}测试类:/ * 一个图书馆出租书的程序。 * 计算每一个读者的消费金额并且打印详情清单。 * 打印信息: * 读者租了哪些书、租期多长、根据租借时间和书的类型算出费用。 * 书分类:普通读本、少儿读本、新书 * 计算费用,以及计算积分。积分根据书的种类是否为新书而又有所不同。 * */public class Test { public static void main(String[] args) { Customer customer = new Customer(); Book book = new Book(“Java入门到放弃”, Book.NEW_RELEASE); Book book1 = new Book(“python入门到放弃”, Book.CHILDRENS); Book book2 = new Book(“golang入门到放弃”, Book.REGULAR); customer.addRental(new Rental(book,8)); customer.addRental(new Rental(book1,4)); customer.addRental(new Rental(book2,6)); customer.setName(“zero”); System.out.println(customer.generateOrder()); }}第一次重构首先:分析一下上面的代码结构:2.假如某天产品跑过来(弄死她吧),需要你增加书的分类规则或者计费规则的时候,上面的代码你怎么做呢。估计又写个差不多方法,然而你会发现其实逻辑跟上面的代码非常相似的。还有更好的办法不?接着:直接看下面的代码重构呗Book类:将按照书的不同类型,按照不同价格统计的方法移动到Book类中,因为这个按理应该属于Book类中的。public class Book { public static final int CHILDRENS = 2; public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; private String title; private int priceCode; public Book() { } public Book(String title, int priceCode) { this.title = title; this.priceCode = priceCode; } //1.提取统计钱的方法 public double getCharge(int daysRented) { double result = 0; switch (getPriceCode()) { case Book.REGULAR: result += 2; if (daysRented > 2) { result += (daysRented - 2) * 1.5; } break; case Book.NEW_RELEASE: result += daysRented * 3; break; case Book.CHILDRENS: result += 1.5; if (daysRented > 3) { result += (daysRented - 3) * 1.5; } break; } return result; } //1.提取计算会员积分的方法 public int getFrequentRenterPoints(int daysRented) { if ((getPriceCode() == Book.NEW_RELEASE) && daysRented > 1) { return 2; } return 1; } 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; }}Rental 类:主要是调用提取统计钱和积分的方法。public class Rental { private Book book; private int daysRented; public Rental() { } public Rental(Book book, int daysRented) { this.book = book; this.daysRented = daysRented; } //1.提取统计钱的方法 public double getCharge() { return book.getCharge(daysRented); } //1.提取计算会员积分的方法 public int getFrequentRenterPoints() { return book.getFrequentRenterPoints(daysRented); } public Book getBook() { return book; } public void setBook(Book book) { this.book = book; } public int getDaysRented() { return daysRented; } public void setDaysRented(int daysRented) { this.daysRented = daysRented; }}Customer 读者类主要是去掉多余的临时变量total,frequentRenterPoints等。public class Customer { private String name; private List<Rental> rentals = new ArrayList(); public Customer() { } public Customer(String name) { this.name = name; } //添加租书信息 public void addRental(Rental rental) { rentals.add(rental); } //生成订单 public String generateOrder() { String result = “Rental Record for " + getName() + “\n”; for (Rental rental : rentals) { result += “\t” + rental.getBook().getTitle() + “\t” + String.valueOf(rental.getCharge()) + “\n”; } result += “Amount owed is " + String.valueOf(getTotalCharge()) + “\n”; result += “You earned " + String.valueOf(getFrequentRenterPoints()) + “frequent renter points”; return result; } //获取购买总数 private double getTotalCharge() { double result = 0; for (Rental rental : rentals) { result += rental.getCharge(); } return result; } //统计积分 private double getFrequentRenterPoints() { double result = 0; for (Rental rental : rentals) { result += rental.getFrequentRenterPoints(); } return result; } public String getName() { return name; } public void setName(String name) { this.name = name; }}最后测试结果跟上面的一样,就是将代码的结果调动一下。现在大致的UML类图如下:第二次重构经过第一次重构,还是没有实现需求修改增加多个分类的效果。那么接下来使用接口抽象来再次重构。Price接口 接口抽象两个规约方法,具体如下public abstract class Price { abstract int getPriceCode(); //1.提取统计总价的方法 abstract double getCharge(int daysRented); //1.提取计算会员积分的方法 public int getFrequentRenterPoints(int daysRented) { return 1; }}RegularPrice 普通的书价格类public class RegularPrice extends Price { @Override int getPriceCode() { return Book.REGULAR; } @Override public double getCharge(int daysRented) { double result = 2; if (daysRented >2) { result += (daysRented - 2) * 1.5; } return result; }}ChildrensPrice 少儿读物类价格public class ChildrensPrice extends Price { @Override int getPriceCode() { return Book.CHILDRENS; } @Override public double getCharge(int daysRented) { double result = 1.5; if (daysRented >3) { result += (daysRented - 3) * 1.5; } return result; }}NewReleasePrice 新书型类价格public class NewReleasePrice extends Price { @Override int getPriceCode() { return Book.NEW_RELEASE; } @Override public double getCharge(int daysRented) { return daysRented * 3; } @Override public int getFrequentRenterPoints(int daysRented) { return (daysRented > 1)?2:1; }}Book类将priceCode换成Price。public class Book { public static final int CHILDRENS = 2; public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; private String title; private Price _price; public Book() { } public Book(String title, int priceCode) { this.title = title; setPriceCode(priceCode); } //1.提取统计数量的方法 public double getCharge(int daysRented) { return _price.getCharge(daysRented); } //1.提取计算会员积分的方法 public int getFrequentRenterPoints(int daysRented) { if ((getPriceCode() == Book.NEW_RELEASE) && daysRented > 1) { return 2; } return 1; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public int getPriceCode() { return _price.getPriceCode(); } public void setPriceCode(int arg) { switch (arg){ case REGULAR: _price = new RegularPrice(); break; case CHILDRENS: _price = new ChildrensPrice(); break; case NEW_RELEASE: _price = new NewReleasePrice(); break; default: throw new IllegalArgumentException(“Incorrect Price code”); } }}最终类图如下:总结大致的工作如下:抽离成方法。移动方法到所属的类。用多态性替换条件。自我封装。用策略替换类型代码。最后想说:如果你发现自己需要为程序添加一个特性,而代码结构使你无法很方便地达成目的,那么就先重构那个程序,使特性的添加比较容易进行,然后再添加特性。写代码就应该像写诗一样,而不是没BUG,我就不动它。参考文章【重构】作者: Martin Fowler最后如果对 Java、大数据感兴趣请长按二维码关注一波,我会努力带给你们价值。觉得对你哪怕有一丁点帮助的请帮忙点个赞或者转发哦。 ...

April 5, 2019 · 5 min · jiezi

代码重构那些事儿

大家好,这是我今天演讲的目录,分Java,JavaScript,ABAP三门编程语言来讲述。Java•JAD•javap•Java Decompiler•Source Monitor•Visual VM•Refactor Menu in EclipseABAP•Code inspector•Refactor feature in AIE•Code coverageJavaScript•ESLint for Fiori Apps•Check Jenkins build log•JSlint for Sublime Text 2•Code check in WebIDE•Profile in Chrome在方法里引入一个布尔类型的参数控制方法的行为,这种做法正确吗?看看stackoverflow上是怎么说的。Java里定义常量的最佳实践:http://developer.51cto.com/ar…Java里这两种定义常量的方法,哪种更好?package one;public interface Constants { String NAME = “孙悟空”; int BP = 10000;}或package two;public class Constants { public static final String NAME = “贝吉塔”; public static final int BP = 9000;}为什么我们不应该在Java 接口中使用Array:https://eclipsesource.com/blo…避免Array的原因之一:Array若使用不当,会造成性能问题避免Array的原因之一:Array若使用不当,会造成性能问题避免Array的原因之二:Array是面向过程编程领域的概念,使用Java面向对象的集合类,比如List,而不是Array看个具体例子:String[] array = { “乔布斯”, “张小龙” };List list = Arrays.asList( array );System.out.println( list );// 打印输出 [乔布斯, 张小龙]System.out.println( array );// -> [Ljava.lang.String;@6f548414list.equals( Arrays.asList( “乔布斯”, “张小龙” ) )// -> truearray.equals( new String[] { “乔布斯”, “张小龙” } )// -> false看出差距了吧?Arrays不是类型安全的!下面的代码能通过编译,但是运行时会报ArrayStoreException的异常:Number[] numbers = new Integer[10];numbers[0] = Long.valueOf( 0 ); 而使用JDK的集合类比如List,就能在编译器即检测出这类错误。Javascript里有趣的逗号function a() { console.log(“I was called!”); return “Jerry”;}var b = a(), a;然后执行下面的代码:console.log(b);会打印出Jerry再看这段代码:var d = (function c(){ return a(),a;})();console.log(d);会打印出:I was called!function a() { console.log(“I was called!”); return “Jerry”;}再看这段代码呢?(function() { var e = f = 1;})();直接报错:Uncaught ReferenceError: f is not definedJavaScript里有趣的分号var b = function(para) { return { doSomething: function() { console.log(“hello: " + para); return para; } }}var a = 1, x = 3, y = 4, ss = a + b(x + y).doSomething() // 打印出 hello: 7console.log(s) // 打印出 8function test(i){ var result = i++; return result}console.log(“test: " + test(3)) // 打印出undefined继续看这段代码s = function(x){ console.log(“called: " + x ); return x}(1 + 2).toString()s = function(x){ console.log(“called: " + x ); return x}(1 + 2).toString()// 打印出 called: 3小技巧 - 如何把您自己增强逻辑植入到legacy遗留代码中var bigFunction = function() { // big logic console.log(“big logic”); // 这句话模拟我们在一段很冗长的遗留代码里植入自己的新逻辑}// 下面这种解决方案不会直接修改遗留函数本身,显得比较优雅var _old = bigFunction;bigFunction = function() { if ( _old ) { _old(); } console.log(“our own enhancement”);}bigFunction();// 第三种解决方案采用了面向切片编程思想,显得更加高级var bigFunction = function() { // big logic console.log(“big logic”);}bigFunction = ( bigFunction || function() {} ).after( function() { console.log(“our own logic”);});bigFunction();如何优雅的在一个函数里增添性能测试统计的工具代码var append_doms = function() { var d = new Date(); // dirty code - nothing to do with application logic!!! for( var i = 0; i < 100000; i++) { var div = document.createElement( “div”); document.body.appendChild(div); } // dirty code - nothing to do with application logic!!! console.log(” time consumed: " + ( new Date() - d));};function test() { append_doms();}传统方案:在充满了业务逻辑的函数体里强行加入红色标准的搜集性能测试的工具代码,这个实现显得很丑陋:再看看采用面向切片编程思路的解决方案:AOP - Aspect Oriented Programmingvar append_doms = function() { for( var i = 0; i < 100000; i++) { var div = document.createElement( “div”); document.body.appendChild(div); }};var log_time = function( func, log_name) { return func = ( function() { var d; return func.before( function(){ d = new Date(); }).after( function(){ console.log( log_name + ( new Date() - d)); }); })(); };function test() { log_time(append_doms, “consumed time: “)();}如何避免代码中大量的IF - ELSE 检查在调用真正的OData API之前,系统有大量的IF ELSE对API的输入参宿进行检查:var send = function() { var value = input.value; if( value.length === ’’ ) { return false; } else if( value.length > MAX_LENGTH) { return false; } … // lots of else else { // call OData API }}更优雅的解决方案:把这些不同的检查规则封装到一个个JavaScript函数里,再把这些函数作为一个规则对象的属性:var valid_rules = { not_empty: function( value ) { return value.length !== ‘’; }, max_length: function( value ) { return value.length <= MAX_LENGTH ; } }实现一个新的检查函数,变量检查对象的属性,执行校验逻辑:var valid_check = function() { for( var i in valid_rules ) { if ( vali_rules[i].apply( this, arguments) === false ) { return false; } }}现在的OData调用函数非常优雅了:var send = function( value ) { if ( valid_check( value ) === false ) { return; } // call OData API}通过这种方式消除了IF ELSE。另一种通过职责链 Chain of Responsibility 的设计模式 design pattern消除IF ELSE分支的代码重构方式:先看传统方式的实现:// Priority: ActiveX > HTML5 > Flash > Form(default)function isActiveXSupported(){ //… return false;}function isHTML5Supported(){ //… return false;}function isFlashSupported(){ //… return false;}好多的IF -ELSE啊:var uploadAPI;if ( isActiveXSupported()) { // lots of initialization work uploadAPI = { “name”: “ActiveX”};}else if( isHTML5Supported()) { // lots of initialization work uploadAPI = { “name”: “HTML5”};}else if( isFlashSupported()) { // lots of initialization work uploadAPI = { “name”: “Flash”};}else { // lots of initialization work uploadAPI = { “name”: “Form”};}console.log(uploadAPI);再看职责链设计模式的实现:Chain of Responsibilityvar getActiveX = function() { try { // lots of initialization work return { “name”: “ActiveX”}; } catch (e) { return null; }}var getHTML5 = function() { try { // lots of initialization work return { “name”: “HTML5”}; } catch (e) { return null; }}代码整洁优雅:var uploadAPI = getActiveX.after(getHTML5).after(getFlash).after(getForm)();console.log(uploadAPI);Java中的Stringpublic class stringTest {public static void main(String[] args) { String userName = “Jerry”; String skill = “JS”; String job = “Developer”; String info = userName + skill + job; System.out.println(info);}}用javap将上面的Hello World程序反编译出来学习:要获取更多Jerry的原创文章,请关注公众号"汪子熙”: ...

February 3, 2019 · 4 min · jiezi

浅谈重构造成的灾难性毁灭

前言这章我在7月20号的时候就准备好了标题,在那之前有写过一篇重构的文章,这段时间一直在等重构造成的弊端。庆幸的是至今也没挂掉。本章我们来聊聊重构造成的灾难性毁灭。青铜只要你确定你是一个真正的程序员,那当你接手一个新项目时,因为每个人的编码规范与风格不同,或者某块代码出现了问题,作为一名向上的程序员,总会想去重构这个项目更严重的都想重写一遍。例如下面的这类代码$status = $_POST[“status”]switch status { case … break; case … break; default: if(){ … }else if(){ … }else(){ … } break;}我知道当你看到这段代码内心是崩溃的,如果是名新人,在没有完全理解其结构作用的情况下,绝对不敢擅自动原有的代码,除非他想加班,那怎么办呢?只好在原有的代码上继续增加代码了。$status = $_POST[“status”]switch status { case … break; case … break; default: if(){ … }else if(){ … }else if(){ … }else (){ // 新人写的 } break;}这块聊的新人入职,一般是不敢动原有代码的。当然这不排除有胆量并且重构的也不错的新人。白银上面聊的与重构并无太大关系,但是必须存在的一段,用于表现程序员重构道路上的勇气。当你到白银差不多需要1-2年的时间,具体时间要个人处在的环境和自学能力。新人不敢动是因为他对语言的基础用法,类库,设计模式都没有特别了解,不敢擅自做动作也是比较聪明、保守的做法。但到了白银就不一样了,我总结了程序员从入门到中级的心理变化,为什么只总结到中级?(PS:作者我自己都没有到高级)这是一个从谦虚到高傲在到什么都不会的过程。看到这里即可明白重构造成的灾难性毁灭是在2-4年的时期发生的,那个阶段在技术不够扎实但还有一股子改变世界的劲头发生的问题。例如积分系统(这里指新接手的项目),领导让你修改签到获得积分,如果在未全面了解代码结构与功能分布时,擅自修改那必然出现一场不可避免的灾难。一个简单的签到功能的模块复杂度不亚于一个普普通通的企业站。大致有如下模块组成用户模块 -> 积分模块 -> 交易模块 -> 明细模块 为什么说是必然?除非你在原有基础上改,那这样你又变回了青铜并且也不想那么做。在基础上重构可能当时不会出现问题。不断的有其他接手早晚回出问题。重构的灾难并非指的是一个人或某个人造成的,就如一个水杯,每个人看到都倒入一滴水,当溢出来时就发生了所谓的“灾难”黄金到这个段位后,很多人都变得聪明了,不在基础代码上修改,更不去所谓的重写。单独拿出一个文件写功能不就好了。事情还远没有那么简单。就如上图那二货,认为自己可以,但最终Over。在项目开发上我们见过很多类型的情况。重构的方式方法有很多种,因人因物(项目)而异,对项目作出合理的分析后再对其作出一部分细节的重构,日月累计最终成形。总结如果你正在做重构应考虑以下几点成本工期代码的优雅与简洁可扩展性等等为什么要重构?原有代码无法更好的扩展,代码可读性差无法在其基础上修改的等等原因。希望会对你提供重构质量有一定帮助。致谢感谢你看到这篇文章,希望可以帮到你,谢谢。

November 16, 2018 · 1 min · jiezi