http://segmentfault.com/a/1190000004360784
封装功能块代码
我有个业务需求,是创建一个div并且渲染数据到页面上,并且根据data的不同,改变div的状态.
http://www.markbernstein.org/Oct13/HillClimbingWonkish.html
封装功能块代码
我有个业务需求,是创建一个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.
这就涉及到另外一个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就是.
但有时候,使用分支,会比这样更简洁,那当然可以使用分支啦。 而使用分支还要主意一个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, 因为每个人站的角度不同,得到的答案当然也不一样。所以这里只介绍两种比较通用的。
- 使用对象来代替参数列表。
- 将需要额外计算的参数忽略。
使用对象代替参数
这个最突出的特点就是在写模板的时候。
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会调哭的。
同样,我要说的是,以为的使用链式的时候,记住,使用一个功能块链式调用一定要分行,不然,调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
- Extract fields, methods, and classes when there is any prospect of eventual reuse by additional methods.
- Do not underestimate the impact of tiny methods on cleaning up your code. Invoking small methods let you omit needless words.
- Extract whenever extracting reduces the length of code — including comments (not counting doc comments required by coding standards, which don’t count).
- Prefer polymorphism to switch(), even if the code is longer.
- Prefer encapsulation to visibility.
- Break dependencies.
- Simplify constructors, even if it complicates the code.
- Encapsulate or avoid conditional expressions. Prefer guard expressions and avoid
else
clauses. The gate is straight down. - 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.
- When in doubt, prefer composition to inheritance .
- 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).
- Prefer small objects, loosely coupled, to large objects and to tight coupling.
- When in doubt, prefer recursion to loops.
- Isolate the network, the database, files, and the user interface with facades, fakes, and humble objects.
- 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.
- Prefer blocks (apply, each, mapcar) to loops.
- Prefer new technology.
Martin在《重构》一书中提到了22个常见的代码坏味道,都可以作为我们重构的目标,来指引我们的重构。如:
- 消除同一类两个方法之间的重复代码
- 消除某一类中的长方法
- 重命名
- 删除A类中的死代码
- 简化复杂的条件语句
同时,重构的范围也应是那段坏味道的代码,在重构过程中对其,也仅对其进行修改。
小菜:我明白了。这一段代码其实我就是想先去除重复问题的,结果改着改着就改到别的地方了。
2.不知道什么时候完工
重构其实不仅有代码级别的重构,还包括模块级别的重构、架构级别的重构。不同级别的复杂度不同,消除的坏味道不同,需要的时间也不同。一般来讲代码级别的重构可以在小时和天以内,架构级别需要的时间会更长一些,比如几周或几月或几年。
3.没有方法,暴力重构
4.没有策略,追求完美主义
重构过程中,经常出现为了消除一个坏味道,改了A类的方法,又改了B类的变量,不得不改了C类;最后发现这三者之间还有依赖,导致进行不下去了,波及面越来越广,时间越来越长,项目经理在催,最后不得不放弃所有的代码。
调整一个正在运行中的系统也如治国,不要期望一次性调整到漂亮的代码或架构,而是要遵循“小步前进”的方法。从问题着手,每次重构一小步。针对一个问题有目的修改,修改完后测试,测试通过后提交代码,再进入下一轮重构。如果在改动过程中发现了其他需要修改的地方,不要顺便重构,你可以把它记下来,作为下一轮重构的内容。
这种做法在代码和模块层面都是相对比较容易实践,而针对架构层次的调整就相对比较复杂。这也是很多架构师需要去思考的问题,如何渐进式重构。不搞一下子半年一年的重构,而是以周以月为单位,快速的迭代,能够很快的验证结果获得收益。
6.只谈招式,不谈心法
《重构》是Martin和Kent对他们多年以来整理代码的实践的总结,然这背后体现的是他们对软件技术的深层次思考和经验。很多新人执着于学习重构手法而疏于学习背后的心法,有些可惜。
Robert C Martin的《代码整洁之道》和《敏捷软件开发:原则、模式与实践》、《设计模式》、Eric的《领域驱动设计:软件核心复杂性应对之道》、《架构之美》等都是帮助大家修炼心法的不错选择,他们可以让你更深层的了解代码,更高层面看待系统,锻炼你的嗅觉,提升你的代码能力。
7.不了解上下文,不与团队沟通
我们不得不承认对代码的重构是有风险的,尤其是模块或架构级别。这段代码的业务是什么,为什么当时这么设计,测试覆盖率是多少,如果这样改会不会影响到其他模块?对其他角色有什么影响?这些问题都要逐一回答。在风险相对较大的改动更要如此,需要和团队成员,各个角色,包括项目经理和客户进行沟通,谈论这次重构的好处和风险,获得足够的评估,从而能够做出合适的重构决策,将风险降到最低。
https://codingstyle.cn/topics/141
http://www.maheshsubramaniya.com/article/when-to-refactor-the-code.html
优秀的程序员对软件设计中存在的坏味道具有敏锐的嗅觉。我所经历的团队,不是因为进度的约束限制重构的工程实践,而是由于缺乏坏味道的嗅觉,长期地麻木不仁。一日皮死麻木不仁,二日肉死针刺不痛,系统就这样每况愈下,腐朽变质了。
孤芳自赏
有的程序员,自我感觉良好,孤芳自赏;实际上,他们往往「不知道自己不知道」,而且这样的程序员心胸都很狭窄,代码犹如尊严一样,绝不容忍侵犯。
直到有一天,知道自己知道了,才觉得那么地无知和可怜。
主次不分
重构是一个系统工程,如果没有目标,主次不分,很容易迷失自我,吃力不讨好。由点到线,由线到面,各个击破;关注优先级,优先解决最棘手,最重要的问题。
设计欠账
重构应该成为每天的面包和黄油,而不是等到程序变成一个大泥球(Big Ball of Mud),才集中人力实施大规模的重构。
交付压力成为拒绝重构最大的借口,重构并不难,关键在于良好的心态,及其持之以恒的工作态度。
保持清洁
Try and leave this world a little better than you found it. - Robert Stephenson
「没有坏就不修改」,技术债务与日俱增,最后穷途末路。相反地,应该遵循「童子军规则」,总是保持提交时的代码比签出时更整洁。决不容忍代码中的脏乱,绝不能倒退到坏习惯中去。
循序渐进
对重构理解越深刻,就越倾向于采用更小的,更安全的步骤。欲速则不达,步子不宜迈得太大,否则容易迷失自我,越挫越不勇,从而害怕重构,拒绝重构,从而使得系统腐败变质,陷入恶性循环。