Monday, September 28, 2015

How to Refactor Code



http://segmentfault.com/a/1190000004360784
封装功能块代码
 我有个业务需求,是创建一个div并且渲染数据到页面上,并且根据data的不同,改变div的状态.
function(data){
    var div = document.createElement("div"),
    div.innerHTML = data.name;
    document.body.append(div);
    if(data.isShow){
        div.style.display = "block";
    }else{
        div.style.display = "none";
    }
}
但事实上,这个函数里面还有一个代码块,就是根据data.isShow改变div的状态.我们可以对其进行封装。
function(data){
    var div = document.createElement("div"),
    div.innerHTML = data.name;
    document.body.append(div);
    changeState(data.isShow,div);
}
function changeState(flag,div){
    div.style.display = flag?"block":"none";
}

提取公因式

这里主要针对于,多次重复调用同一个封装代码块函数。
function(flag){
    if(flag==="left"){
        move("right");
    }else if(flag==="right"){
        move("left");
    }else if(flag==="top"){
        move("bottom");
    }else if(flag==="bottom"){
        move("top");
    }
}
根据flag向反方向移动,可以看出,里面都有用到了move()这个方法,要知道,分支语句是最不利于程序阅读的,而且我们要尽可能的减少和简化分支语句里面的程序量,让阅读者耗费在分支语句上的时间降到最少。上面代码可以这样写。
function(flag){
    var dir;
    if(flag==="left"){
        dir = "right";
    }else if(flag==="right"){
        dir = "left";
    }else if(flag==="top"){
        dir = "bottom";
    }else if(flag==="bottom"){
        dir = "top";
    }
    move(dir);
}
恩,当然,这样写也是违反人性的。我们可以使用命令模式进行重构。
这就涉及到另外一个tip.

将分支转化为函数

上面代码里面的分支完全可以使用函数来进行代替。
function(flag){
    command.flag;
}
var command = (function(){
    var left = function(){
        move("right");
    }
    var right = function(){
        move("left");
    }
    var top = function(){
        move("bottom");
    }
    var bottom = function(){
        move("top");
    }
    return {
        left,right,top,bottom
    }
})();
这样,虽然增加了一个对象,但是代码确实清晰可见的。 这就是通过命令模式,来重构代码,完成性能和阅读的优化。
但有时候,使用分支,会比这样更简洁,那当然可以使用分支啦。 而使用分支还要主意一个tip就是.

不要过度嵌套

这里想说的就两点,一是,尽可能不使用分支,二是,如果嵌套分支,尽量改为不嵌套。 不使用分支的情况上面已经说了,如果使用分支,那么请不要嵌套,或者说不要过度嵌套。因为一个分支已经很难阅读了,md,你再加个嵌套,你还让不让人读了。 而解决过度嵌套的方法真的是千千万万,我这里就介绍一个比较简单的。使用return 提早退出嵌套。
function move(obj){
    if(obj.isShow){
        if(obj.isTop){
            if(obj.isLeft){
                return move("TopLeft");
            }
        }else{
            return false;
        }
    }else {
        return false;
    }
}
这,看着爽不爽。 如果,我遇见这样的代码,我第一反应就是,要!死!啦!. 所以,为了让你的程序人性化,我们可以使用return 语句进行改写。 我们可以对条件判断的逻辑进行分析,可以看出,里面如果条件不满足都是返回false,那么我们可以将false的情况提取出来。
function move(obj){
    if(!obj.isShow){
        return false;
    }
    if(!obj.isTop){
        return false;
    }
    if(!obj.isLeft){
        return false;
    }
    return move("TopLeft");
}
这是这个feel。当然,追求极致的话,我们可以看出return false,是完全一致的,当然可以将条件合并.
function move(obj){
    if(!obj.isShow||!obj.isTop||!obj.isLeft){
        return false;
    }
    return move("TopLeft");
}
其实如果你数学学得好的话(我还行吧,嘿~嘿~嘿~)。 这样提取条件的事是轻而易举的,可以看出,上面那段古老的代码完全可以变为现在这个样式,而且读起来,真的不是一个档次的。

减少参数数量

减少参数数量的方法,当然永远不会===1, 因为每个人站的角度不同,得到的答案当然也不一样。所以这里只介绍两种比较通用的。
  1. 使用对象来代替参数列表。
  2. 将需要额外计算的参数忽略。

使用对象代替参数

这个最突出的特点就是在写模板的时候。
function templ(name,age,gender){
    return `my name is ${name}. and I'm ${age} years old. yeah! I am a ${gender}`;
}
有一个模板,上面需求的参数有三个,但是,事实上,这个是完全不靠谱的。 因为一个人不仅仅只有name,age,gender 肯定还有别的参数,这样,造成的后果就是,你一直在维护模板的同时,还需要维护参数列表。而且,还要维护,传入参数的顺序的正确性。所以这里强烈推荐使用对象来代替多参数。
function templ(person){
    return `my name is ${person.name}. and I'm ${person.age} years old. yeah! I am a ${person.gender}`;
}
现在这个模板函数与外界的耦合性已经降低了不少。而且非常易于维护,就算外面你的person对象有多余的参数,也不会妨碍我使用我需要的数据。

忽略额外计算的参数

这种情况主要是在做UI的时候可能会遇到,即,想绘制一个数据table的时候,需要将一个数据矩形的高,宽以及面积传入一个函数,进行绘制。
function column(width,height,square){
    console.log("矩形的宽度为"+width);
    console.log("矩形的高度为"+height);
    console.log("矩形的面积为"+square);
}
而,这样做是完全没有必要的,因为函数参数越少,给人的感觉当然越好。
我们可以修改为这样.
function column(width,height){
    var square = width*height;
    console.log("矩形的宽度为"+width);
    console.log("矩形的高度为"+height);
    console.log("矩形的面积为"+square);
}
而且在插件设计中,也应该准遵守这个原则,函数的参数应该在能力范围内,把它降至最少。

链式调用

这个应该算是比较高级的用法。使用过jQuery的同学应该印象最深刻。 即,我们可以这样来使用一个功能.
$(".myClass").addClass("show").attr('data-name').css("display","none");
而这样实现其实并不难,只要在每个方法的后面返回该对象就可以实现这个技能。
我们来模仿一下。
var Div = function(){

}
Div.prototype.createElement = function(){
    console.log("创建一个Div");
    return this;
}
Div.prototype.showDiv = function(){
    console.log("显示Div");
    return this;
}
var div = new Div();
div.createElement().showDiv();
这样不仅可以实现对象的细粒度,而且也满足单一职责原则。
同样,我要说的是,以为的使用链式的时候,记住,使用一个功能块链式调用一定要分行,不然,调bug会调哭的。
var Div = function(){

}
Div.prototype.createElement = function(){
    console.log("创建一个Div");
    return this;
}
Div.prototype.showDiv = function(){
    console.log("显示Div");
    return this;
}
Div.prototype.hideDiv = function(){
    console.log("隐藏Div");
    return this;
}
Div.prototype.tagName = function(){
    console.log("tagName 是 Div");
    return this;
}
var div = new Div();
div.createElement().showDiv().tagName().hideDiv();  //表这样做
上面是个反例,正确的做法,应该分开。
div.createElement()
.showDiv()
.tagName()
.hideDiv();
像这样调用,万一出个Bug,你也应该知道这个bug在哪一个函数块内。
http://www.markbernstein.org/Oct13/HillClimbingWonkish.html
  1. Extract fields, methods, and classes when there is any prospect of eventual reuse by additional methods.
  2. Do not underestimate the impact of tiny methods on cleaning up your code. Invoking small methods let you omit needless words.
  3. Extract whenever extracting reduces the length of code — including comments (not counting doc comments required by coding standards, which don’t count).
  4. Prefer polymorphism to switch(), even if the code is longer.
  5. Prefer encapsulation to visibility.
  6. Break dependencies.
  7. Simplify constructors, even if it complicates the code.
  8. Encapsulate or avoid conditional expressions. Prefer guard expressions and avoid else clauses. The gate is straight down.
  9. Prefer a dedicated type to a primitive, even if the code is longer; it’s better to pass Money than an integer even if you know the integer is money.
  10. When in doubt, prefer composition to inheritance .
  11. When in doubt, move computation to the object that owns the data or move the data to the object that does the computation (aka Law of Demeter).
  12. Prefer small objects, loosely coupled, to large objects and to tight coupling.
  13. When in doubt, prefer recursion to loops.
  14. Isolate the network, the database, files, and the user interface with facades, fakes, and humble objects.
  15. When in doubt, add code to the model if you can, to the controller if you must. Regard any work in the view as a convenience function or syntactic sugar, but don’t let that stop you.
  16. Prefer blocks (apply, each, mapcar) to loops.
  17. Prefer new technology.
http://insights.thoughtworkers.org/refactoring/
Martin在《重构》一书中提到了22个常见的代码坏味道,都可以作为我们重构的目标,来指引我们的重构。如:
  • 消除同一类两个方法之间的重复代码
  • 消除某一类中的长方法
  • 重命名
  • 删除A类中的死代码
  • 简化复杂的条件语句
同时,重构的范围也应是那段坏味道的代码,在重构过程中对其,也仅对其进行修改。
小菜:我明白了。这一段代码其实我就是想先去除重复问题的,结果改着改着就改到别的地方了。

2.不知道什么时候完工

重构其实不仅有代码级别的重构,还包括模块级别的重构、架构级别的重构。不同级别的复杂度不同,消除的坏味道不同,需要的时间也不同。一般来讲代码级别的重构可以在小时和天以内,架构级别需要的时间会更长一些,比如几周或几月或几年。

3.没有方法,暴力重构

4.没有策略,追求完美主义

重构过程中,经常出现为了消除一个坏味道,改了A类的方法,又改了B类的变量,不得不改了C类;最后发现这三者之间还有依赖,导致进行不下去了,波及面越来越广,时间越来越长,项目经理在催,最后不得不放弃所有的代码。
调整一个正在运行中的系统也如治国,不要期望一次性调整到漂亮的代码或架构,而是要遵循“小步前进”的方法。从问题着手,每次重构一小步。针对一个问题有目的修改,修改完后测试,测试通过后提交代码,再进入下一轮重构。如果在改动过程中发现了其他需要修改的地方,不要顺便重构,你可以把它记下来,作为下一轮重构的内容。
这种做法在代码和模块层面都是相对比较容易实践,而针对架构层次的调整就相对比较复杂。这也是很多架构师需要去思考的问题,如何渐进式重构。不搞一下子半年一年的重构,而是以周以月为单位,快速的迭代,能够很快的验证结果获得收益。

6.只谈招式,不谈心法

《重构》是Martin和Kent对他们多年以来整理代码的实践的总结,然这背后体现的是他们对软件技术的深层次思考和经验。很多新人执着于学习重构手法而疏于学习背后的心法,有些可惜。
Robert C Martin的《代码整洁之道》和《敏捷软件开发:原则、模式与实践》、《设计模式》、Eric的《领域驱动设计:软件核心复杂性应对之道》、《架构之美》等都是帮助大家修炼心法的不错选择,他们可以让你更深层的了解代码,更高层面看待系统,锻炼你的嗅觉,提升你的代码能力。

7.不了解上下文,不与团队沟通

我们不得不承认对代码的重构是有风险的,尤其是模块或架构级别。这段代码的业务是什么,为什么当时这么设计,测试覆盖率是多少,如果这样改会不会影响到其他模块?对其他角色有什么影响?这些问题都要逐一回答。在风险相对较大的改动更要如此,需要和团队成员,各个角色,包括项目经理和客户进行沟通,谈论这次重构的好处和风险,获得足够的评估,从而能够做出合适的重构决策,将风险降到最低。
https://codingstyle.cn/topics/141
优秀的程序员对软件设计中存在的坏味道具有敏锐的嗅觉。我所经历的团队,不是因为进度的约束限制重构的工程实践,而是由于缺乏坏味道的嗅觉,长期地麻木不仁。一日皮死麻木不仁,二日肉死针刺不痛,系统就这样每况愈下,腐朽变质了。

孤芳自赏

有的程序员,自我感觉良好,孤芳自赏;实际上,他们往往「不知道自己不知道」,而且这样的程序员心胸都很狭窄,代码犹如尊严一样,绝不容忍侵犯。
直到有一天,知道自己知道了,才觉得那么地无知和可怜。

主次不分

重构是一个系统工程,如果没有目标,主次不分,很容易迷失自我,吃力不讨好。由点到线,由线到面,各个击破;关注优先级,优先解决最棘手,最重要的问题。

设计欠账

重构应该成为每天的面包和黄油,而不是等到程序变成一个大泥球(Big Ball of Mud),才集中人力实施大规模的重构。
交付压力成为拒绝重构最大的借口,重构并不难,关键在于良好的心态,及其持之以恒的工作态度。

保持清洁

Try and leave this world a little better than you found it. - Robert Stephenson
「没有坏就不修改」,技术债务与日俱增,最后穷途末路。相反地,应该遵循「童子军规则」,总是保持提交时的代码比签出时更整洁。决不容忍代码中的脏乱,绝不能倒退到坏习惯中去。

循序渐进

对重构理解越深刻,就越倾向于采用更小的,更安全的步骤。欲速则不达,步子不宜迈得太大,否则容易迷失自我,越挫越不勇,从而害怕重构,拒绝重构,从而使得系统腐败变质,陷入恶性循环。
http://www.maheshsubramaniya.com/article/when-to-refactor-the-code.html

Many Incremental changes are better

If the code you are going to re-factor leads to refactoring other code and it chains reaction. You should stop. Turn your focus on to the other modules and re-factor incrementally.

Follow coding standards of your company

This is like talking same language to anyone reading your code. If you break the coding standards, then you break the grammar and people have difficulty in understanding and if someone doesnt understand what you have done, then it voids your work.

No ego in personal preference

And don’t get your personal preference into the code. If you think that what you are doing is awesome, then take it to the standards group and then get it done. Don’t sneak it.

Language gets a new API

Good thing to know, but still doesn’t make a solid case to re-factor your code. It is probably the best candidate for new code.

Labels

Review (572) System Design (334) System Design - Review (198) Java (189) Coding (75) Interview-System Design (65) Interview (63) Book Notes (59) Coding - Review (59) to-do (45) Linux (43) Knowledge (39) Interview-Java (35) Knowledge - Review (32) Database (31) Design Patterns (31) Big Data (29) Product Architecture (28) MultiThread (27) Soft Skills (27) Concurrency (26) Cracking Code Interview (26) Miscs (25) Distributed (24) OOD Design (24) Google (23) Career (22) Interview - Review (21) Java - Code (21) Operating System (21) Interview Q&A (20) System Design - Practice (20) Tips (19) Algorithm (17) Company - Facebook (17) Security (17) How to Ace Interview (16) Brain Teaser (14) Linux - Shell (14) Redis (14) Testing (14) Tools (14) Code Quality (13) Search (13) Spark (13) Spring (13) Company - LinkedIn (12) How to (12) Interview-Database (12) Interview-Operating System (12) Solr (12) Architecture Principles (11) Resource (10) Amazon (9) Cache (9) Git (9) Interview - MultiThread (9) Scalability (9) Trouble Shooting (9) Web Dev (9) Architecture Model (8) Better Programmer (8) Cassandra (8) Company - Uber (8) Java67 (8) Math (8) OO Design principles (8) SOLID (8) Design (7) Interview Corner (7) JVM (7) Java Basics (7) Kafka (7) Mac (7) Machine Learning (7) NoSQL (7) C++ (6) Chrome (6) File System (6) Highscalability (6) How to Better (6) Network (6) Restful (6) CareerCup (5) Code Review (5) Hash (5) How to Interview (5) JDK Source Code (5) JavaScript (5) Leetcode (5) Must Known (5) Python (5)

Popular Posts