在代码开发当中,无论使用什么语言,在没有严格秩序的三层项目当中,往往都会重复着代码变屎山的过程,我们亲眼看到自己从0到1的项目随着代码堆叠慢慢变得臃肿,却很难有效的改变现状。
从历史规律来看,这似乎是人的问题,没有写好代码,但我们拉高视角,似乎这更是一个开发体系潜移默化的选择,选择了容忍臃肿和泛滥来换取所谓的开发迭代效率和需求功能产出。
这就像是盖楼少了一半的预算,工人不得从水泥钢筋削减成本,少了一半的工期,工人不得不简化设计一切只为尽快达成目的,而最后收尾复盘将所有一切项目质量的责任追究到工人头上,事实上是个笑话。
那么我们必须承认一个事实,优秀的代码往往需要更好的人力来实现,更长的时间来守序,这意味着更高的成本,从一开始这仅仅是一个简单的买单倾向问题。
抛开背景,我们从代码实现开始,一步一步开始看,我们的代码是怎么被腐化的。
一开始,有了一个需求,需要增删改查订单信息,于是张三写了如下方法:
// 添加订单
public bool addOrder(AddOrderInput input)
{
if(_orderRepository.GetExist(input.OrderId)){ throw Exception("订单号已存在")};
_orderRepository.AddOrder(new OrderMaster(){ ... });
}
后来,有个团队希望能够通过CRM生成订单,张三于是添加了方法:
// 添加Crm订单
public bool addCrmOrder(AddCrmOrderInput input)
{
var order = new OrderMaster(){ ... };
order = this.ConvertCrmOrder(input); // 转换CRM订单为后台订单模型
this.addOrder(order); // 调用上方方法
}
// 转换模型
public AddOrderInput ConvertCrmOrder(AddCrmOrderInput input);
然后,产品修改需求,CRM的订单存在子单多插入,要求你针对这个来源不用卡控单号唯一。
于是,张三开始修改addOrder方法:
public bool addOrder(AddOrderInput input)
{
if(input.Source != SourceEnum.Crm && _orderRepository.GetExist(input.OrderId))
{
throw Exception("订单号已存在")
};
return _orderRepository.AddOrder(new OrderMaster(){ ... });
}
我们很容易看出,input.Source != SourceEnum.Crm 这行代码已经对addOrder构成逻辑侵入。
但现实这是高速实现需求的最快途径,因为产品要求让张三一分钟改完上线。
然后,我们又有了来自OA的订单,我们需要针对OA订单生成单号:
public bool addOrder(AddOrderInput input)
{
if(input.Source == SourceEnum.OA)
{
input.OrderId = this.GeneratorOrderId();
}
else if(input.Source != SourceEnum.Crm && _orderRepository.GetExist(input.OrderId))
{
throw Exception("订单号已存在")
};
return _orderRepository.AddOrder(new OrderMaster(){ ... });
}
然后,产品要求订单创建后,推送ESB消息到企业微信:
张三二话不说加了几行代码:
public bool addOrder(AddOrderInput input)
{
if(input.Source == SourceEnum.OA)
{
input.OrderId = this.GeneratorOrderId();
}
else if(input.Source != SourceEnum.Crm && _orderRepository.GetExist(input.OrderId))
{
throw Exception("订单号已存在")
};
var res = _orderRepository.AddOrder(new OrderMaster(){ ... });
if (res) {
_esb.PublishMsg(new MsgDto(){...});
}
return res;
}
时间慢慢推移,公司外购了一套订单系统,要求老旧系统并用一段时间。
由此张三需要同步新系统订单到老系统,但新系统订单已闭环,你不必推送消息。
此时,张三为了防止对老系统产生影响,于是就CV了一个新的方法出来:
public bool addOrder2(AddOrderInput input)
{
if(input.Source == SourceEnum.OA)
{
input.OrderId = this.GeneratorOrderId();
}
else if(input.Source != SourceEnum.Crm && _orderRepository.GetExist(input.OrderId))
{
throw Exception("订单号已存在")
};
var res = _orderRepository.AddOrder(new OrderMaster(){ ... });
//if (res) {
//_esb.PublishMsg(new MsgDto(){...});
//} 注释大法YYDS
return res;
}
然后这样对外服务:
public bool addOrder(AddOrderInput input){
if(input.Mode == OrderMode.NewSys){
this.addOrder2(input);
} else {
this.addOrder(input);
}
}
张三心里乐开了花,两个理由:
我没有修改addOrder一行代码,对老系统逻辑0修改
我新系统用的全新的addOrder2方法,完全新代码
后台,因为外购系统同步订单是定时批量,并发太大,张三立马针对外购订单加上了操作锁:
public bool addOrderMain(AddOrderInput input){
if(input.Mode == OrderMode.NewSys){
if (_redis.Lock(input.orderId)) {
try{
this.addOrder2(input);
} finally {
_redis.releaseLock(input.orderId);
}
} else {
throw new Expeption($"订单{input.orderId}正在添加")
}
} else {
this.addOrder(input);
}
}
然后,外购系统要求张三提供一个add接口供他们调用,外部提供的信息里并没有mode信息,张三于是又开始了新的创造:
public bool addOrderMain2(AddOrderInput input){
if (_redis.Lock(input.orderId)) {
try{
this.addOrder2(input);
} finally {
_redis.releaseLock(input.orderId);
}
} else {
throw new Expeption($"订单{input.orderId}正在添加")
}
}
随即,产品觉得需要在添加订单时有一条日志,张三于是在addOrder3加了日志:
public bool addOrderMain2(AddOrderInput input){
if (_redis.Lock(input.orderId)) {
try{
this.addOrder2(input);
this.addOrderLog(input);
} finally {
_redis.releaseLock(input.orderId);
}
} else {
throw new Expeption($"订单{input.orderId}正在添加")
}
}
随后,张三被提了bug,因为发现同步订单addOrderMain没有加日志
张三于是干脆直接手起刀落,直接在addOrder2里加了日志:
public bool addOrder2(AddOrderInput input)
{
if(input.Source == SourceEnum.OA)
{
input.OrderId = this.GeneratorOrderId();
}
else if(input.Source != SourceEnum.Crm && _orderRepository.GetExist(input.OrderId))
{
throw Exception("订单号已存在")
};
var res = _orderRepository.AddOrder(new OrderMaster(){ ... });
if (res) {
//_esb.PublishMsg(new MsgDto(){...});
this.addOrderLog(input);
}
return res;
}
然后,测试又提了bug,来自外部调用的订单日志重复了
张三一看,addOrderMain2和addOrder2似乎重复写了日志,蓝瘦香菇。
于是,张三干脆加了个判断:
public bool addOrder2(AddOrderInput input, bool isMain2)
{
if(input.Source == SourceEnum.OA)
{
input.OrderId = this.GeneratorOrderId();
}
else if(input.Source != SourceEnum.Crm && _orderRepository.GetExist(input.OrderId))
{
throw Exception("订单号已存在")
};
var res = _orderRepository.AddOrder(new OrderMaster(){ ... });
if (res) {
//_esb.PublishMsg(new MsgDto(){...});
// 如果不来自addOrderMain2就不加日志防止重复加
if(isMain2){
this.addOrderLog(input);
}
}
return res;
}
然后:
public bool addOrderMain2(AddOrderInput input){
if (_redis.Lock(input.orderId)) {
try{
// 外部订单,加日志
this.addOrder2(input, true);
// this.addOrderLog(input);
} finally {
_redis.releaseLock(input.orderId);
}
} else {
throw new Expeption($"订单{input.orderId}正在添加")
}
}
再然后:
// 同步订单,不加日志
public bool addOrder(AddOrderInput input){
if(input.Mode == OrderMode.NewSys){
this.addOrder2(input, false);
} else {
this.addOrder(input);
}
}
故事还没走完,我们向上看代码,此时添加订单的逻辑已经变得异常臃肿,通过不断的嵌套调用,内外参数传值分流逻辑已经变得难以维护。
然而张三不服,他说他的方法通俗易懂,像流水账一样从上到下往下看就行了,没有任何结构性理解成本。