看到项目中存在的烂代码时一般都是一个头两个大,要么不想触碰,要么只想重写,然后那边出了个bug,于是我们一边硬着头皮改一边骂娘。
但是往往烂代码都是想一个被扔了几片纸屑,几根香蕉皮的角落,你路过再丢一把瓜子壳,他扔几个塑料袋,然后这个角落就变成了臭气熏天的垃圾堆,大家都是捂着鼻子迅速逃离,没有人愿意去清理。有天来了个新人,看到这里实在太脏乱差,一股脑把这些垃圾全部铲除,世界瞬间清净了。
一般来说我们都愿意去铲除垃圾,而不愿意去进行垃圾分类,垃圾分类的好处大家都知道,但是却甚少人去做,所以其实接手烂代码最重要的一点我认为是:态度
劣根性
入职现在的公司时iOS有四个小伙伴,第一次有了Team Leader(Jacky),之前都是只有自己,什么都要自己解决,当时觉得异常轻松。
一次有个bug解决不了,里面的数据库逻辑实在复杂,每次我看到一半都看不下去了,于是放弃,后来觉得老是依赖别人也不行,于是硬着头皮去看了,一句句debug,最终找到问题所在,其实也很简单,是一句sql的字段写得有问题,最终解决了。其实当时解决了问题我内心并不是非常高兴,因为我的心态其实是这个问题很复杂,超出了我的能力范围,而最终发现并不是这样,心情很复杂,为自己推脱偷懒找借口,而且回想起过去一年什么问题都是自己想尽办法解决的经历,现在实在是太松懈了。
那段时间团队其实一直处于1+1<1的状态,甚至Jacky还要解决很多我们埋下的坑,甚至没有他一个人效率高。那时我要去解决烂代码就是删掉一部分然后重写,总觉得这写得什么渣,自己写一定不会写成这屎样,但是每次写的时候都发现逻辑异常得多,写着写着自己的代码也变成了别人眼中的烂代码,如此恶性循环。。
摆正态度
后来很长一段时间之内,我都会仔细去读项目中很烂的代码,可能一次没有看懂,下次看到了会再去看,后来就是今天对一部分代码进行简单的精简,即合并相似代码,多合并几次之后,再看到那部分代码就会顺眼很多,隔一段时间之后再看,可能又有些别的想法,再进行提炼封装,一般这样的话,下次有空我就会把可以抽离成单独模块的部分从原有的结构中剥离出来,慢慢的原有的类会缩减不少代码和逻辑。
去年一年态度摆正之后,抽丝剥茧的能力提升了很多,到今年开始写代码时就会慢慢照着原来剥离代码的方式去写,进行接口编程,团队也扩大到了7人,合作写代码时,为别人提供接口时定义好参数和返回值,如后台给我们提供接口一般。
整理烂代码
看到烂代码时不要急着去否定,可以慢慢进行优化,也不用想着一下子就优化得很到位,毕竟时间也不是非常允许的,所以我现在一般都是看到一点改一点,慢慢进行梳理,我一般按照下面的顺序来。
多使用常量定义
tag = 999;if(tag == 999){
}
看到一堆这样的不知所谓的数字重复出现多次时,去弄清楚它的含义,使用常量命名,如果只是在当前方法中使用,在方法体中定义这个常量:
-(void)handleViews:(NSUInteger)tag{
NSUInteger const tagBtnDelete = 999;
if(tag == tagBtnDelete){
// 功能代码
}
}
如果只在当前类中使用,在.m中定义这个常量:
static NSUInteger const tagBtnDelete = 999;
@implemetation TestView
-(void)handleViews:(NSUInteger)tag{
if(tag == tagBtnDelete){
// 功能代码
}
}
@end
如果需要在多个类中使用,在.m中定义,在.h中声明外部变量:
.m文件如上,.h
extern NSUInteger const tagBtnDelete;
@interface TestView:UIView
@end
这样暴露出来的tagBtnDelete的值便是不可修改的,在.h中暴露的信息越少越好。
比如还有很多 类似的取字典的key值,每次都去写一遍@“XXXKey”这样的方式非常容易出错,导致严重的傻逼bug(别问我怎么知道的),同样按照上述方法定义既可。
switch 中 case 多的情况,见过非常多的直接进行数字比较,定义成枚举。
一般我第一步就先做这种简单的替换操作,可能有时候一次都只定义一个常量进行替换,但是下次再看到时意思就明朗很多。
命名保持统一
命名其实对代码可读性非常重要
- 命名要保持统一
一个模块保持同一命名前缀,代码中不要出现两种命名 - 命名要通俗易懂
不要使用太生僻的词汇,有些可以用中文拼音(比如 hongbao ),但是求你了,千万不要中英文混拼,尤其不要使用中文拼音首字母与英文进行混拼。 - 命名要能表达一定的信息
比如tag,直接使用tag的话意思还是不明朗的* 常量使用K打头 这个应该属于iOS中不成文的规定了,其他语言不清楚,忽略。 - 团队中可以定义一种通用命名顺序
比如我们团队中就是 将类型放在前面,比如viewXXX , tableXXX, lblXXX(lbl代表label,约定好的)等等,这样其他人调用你的代码时会比较方便。
终极case,照着苹果的官方代码写就对了~~~
多使用中间变量进行传值
看到有人问这样的问题,见截图,这样的崩溃会体现在一行
如果将代码的每个参数都分开定义成中间变量,那么XCode的崩溃就会定位到正确的地方,如下:
NSInteger location = [matchPos[i] intValue];
NSInteger length = 1;
NSRange range = NSMakeRange(location, length);
[highPhone addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:range];
如果按照这样的写法,在第一行代码就会崩溃。
像上面的 [UIColor redColor]这样的参数可以直接传,当然也可以再加一个中间变量再传,因为是调用系统级的倒也无所谓,如果是自己写的方法,还是建议添加中间变量,否则在debug时会非常的不方便。
记得上大一时觉得写这样的代码非常炫酷,因为只有一行,但是其实一位追求行数是不科学的,清晰的表达更重要,所以尽量不要使用嵌套多个方法来写代码,图一时方便,增加了debug的成本,而且代码可读性也非常差。
整理逻辑不清楚重复的代码
对于多个if else的情况(如果case很复杂,其实一般是设计类和接口就出了问题,但是有时候牵涉甚广,一时无法进行重构,先进行简单整理),有时候会出现有些case已经包含了后面所写的逻辑,那么就要将case整理干净一点,尽量不要出现有重复调用的情况,如果发现逻辑实在是复杂,必须出现那样的情况,或是一时半会理不清楚,可以先进行注释分析,下次说不定就有思路了。
提炼功能类似的代码
前面几步做掉以后,如果发现有风格非常类似的代码块,可以进行简单的合并同类项,提炼不同的部分作为参数,将重复的部分封装有些人可能会说,其实就是个简单的封装啊,说那么多干嘛
以我个人的经验来说,前面几步其实很重要,我发现很多时候将常量变量定义清楚,代码精简以后,这些重复的特征会非常明显的显现出来,有时候都不用你刻意去封装,觉得那个方法已经就出现在那里了,只等你去命个名。而乱七八糟地堆在那里,鬼都看不懂是什么,更别提动手去封装了
解耦,剥离子模块
代码块的封装完成后,就可以进行模块的封装了,单独的控件,重复的逻辑,抽丝剥茧一般我是先写.h文件,假设自己是一个要调用这个模块的人,我想要调用什么样的方法,得到什么样的效果,定义好所需要的方法和属性之后,写好测试代码,再去写.m文件,然后测试是否达到了所需要的效果。
剥离子模块时有一个思维定势,有时候会看着原来的代码想这怎么剥得出来,想着想着就晕了,觉得千丝万缕理不清楚,我推荐忘记那些原有的实现,重新去设计这个类,放心,等去写.m文件时用得着原来的代码,工作量不会很大的。
站在用户的角度去设计,是最佳的,或者拉一个同伴,与他讲解你的设计,他可能会给你提出更好的方案。忘掉那些烂代码,设计出新的实现方式,子模块一个个剥离之后,烂代码的整理也就算完成了,你会发现那块地方已经不再是脏乱差了。能够整理好烂代码,写代码的能力必然也是不会差的,既然剥离了子模块,那么在代码设计时以后自然也会去进行模块化了。所以不要拒绝烂代码,不管是自己的还是他人的。
减少烂代码
今年年初,公司启动了techDesign,也就是每次在写某部分代码之前,先写技术文档,想好一些结构和逻辑,各个模块之间的关系,数据库设计,缓存机制等,这种方式非常有力地减少了烂代码的产生,写到哪算哪这样的方式非常容易让自己的代码成为车祸现场,CTO不止一次地跟我们说过不要急着写代码,要先想清楚,写好TechDesign之后,我们会上传,团队中的每一个人都可以看到,有问题及时和同伴讨论,相当与是一次简单的架构。
虽然说最终写的时候会与TechDesign中写的有很大不同,但是这个文档本就不是最终稿,也不是用来禁锢思维,而是理清思路的一种方式,最终写代码的过程中肯定也会添加不少细节和技术实现。
后记
听了那么多道理,却仍然过不好这一生
道理我都懂,但是臣妾做不到
说教总是容易的
要是我早点看到这本书
要是再让我上一次大学
要是再给我一次选择的机会
要是...
是的,刚入职场时前辈们与我们分享的经验教训,都是他们的亲身经历,苦口婆心,但是我们却异常冷静与麻木,那些他们走过的弯路你一定要再走一遍,那些他们铲除过的荆棘,你毫不遗漏地全部再整理一遍,然后我们变成了和他们一样苦口婆心地劝解别人的人。但是有前辈的指引,你即便不能走最快的路,至少可以少绕几个拐角,少被扎几次。
一切都要靠自己付出行动后才知道.