一点coding的心得

以下是我在工作过程中思考得到的一点感悟,相信我,遵循下面的原则,生活会更加的美好。星爷不是说:

只要能好一点点,那就要好一点点

好了,废话到此结束,写起:

一、无重复

在保证代码整洁的基础上,<strong>无重复</strong>是好代码追求的首要目标。因为<strong>无重复实在是太重要了</strong> ,如果整篇文章的内容你只能记住几个字,那就该是:无重复。好了,在你脑袋被我弄傻之前,让我们来讲讲为啥要做到无重复。

你一定见过这样的场景:

某个程序员要做一个功能,于是他先找到类似的代码贴过来,再根据自己的理解进行修改,改完运行一遍,看看结果
恩,不错,搞定~~

我不得不说:在我刚刚工作的时候,有那么两个月也是这么来干的。

这种拷贝粘贴的做法,唯一的好处是:如果你处于新手阶段,当你面对一个具体的功能A,即使你没有完整的思路,也可以通过分析A与其它类似业务的差异性,屏蔽相同或者相似部分,通过试错只对有差异部分做修改就能快速完成任务。粘贴拷贝,真乃神器,真乃福音。

<strong>但请记住,就是拷贝粘贴导致“重复代码”大量的出现</strong>。

虽然看起来好处不小,但它的好处就如同晨露一般短暂,而它带来的副作用却往往是长远和灾难性的。

这绝不是危言耸听:

在我曾经经历过一个项目中,报表模块由于设计上的失误,我所负责的地方,有二十多份相似的拷贝,每一个拷贝负责一张报表的功能。这些拷贝们业务逻辑大量重复,只在少数参数和输出图形形式(饼图或者柱状图或者曲线)上存在差异。

试想如果每份代码只是编写完成就不需要修改还好,但是需求嘛,你们懂得,变化总是来得那么猛烈,我需要频繁的修改二十多份不同的拷贝,并且拷贝的数量还在成上升趋势,这导致了什么呢?

即使像改变初始值这种微不足道的修改,我都需要十分钟甚至更久来维护二十多份重复的代码,毕竟人不是机器,总是难免漏掉那个几个忘了修改,于是不一致性表现了出来,bug从天而降---我要花上拷贝一千倍的时间来断点调试debug,并且经常性的发生失误(改错拷贝了,把对的改错了)同时忍受着测试“大妈”说:“怎么又错了,刚刚不是好好的?”苦逼二字用来形容此刻的心情真是太TMD合适了。

很快我变得筋疲力尽,我厌恶甚至是憎恨这些重复的代码,他们让那种简单分析一下就能定位错误的美好愿景变得无从谈起~这种痛苦,只可意会,这种经历,终身难忘……

很好,你现在和我一样厌倦了重复,那么该怎么干呢?

相同的逻辑永远在上层,而差异散列在下层

上层代码并不局限于父类,它也可以是模板模式中的模板类等等,记住不要局限于任何形式化的方法,灵活一点,只要你想到一种方式可以避免重复,那就是ok的。

无重复,之所以重要不仅因为它可以解放我们,还有一个更重要的原因:它可以升华我们。

为了做到无重复,你要努力的从纷繁复杂的表象中抽象出相同的逻辑,这种能力是所有OO设计的能力的基础,你所有的设计手段将源自于此。做到无重复,是成为高手的敲门砖。

好了,这就是我要说的第一点:无重复。我相信肯定很多人仍然是这么干的,但是请不要灰心,这对一个新手是必须经历的过程,努力跳出来,好日子等着你。

如果你还感兴趣,山治说:第二道菜来了。

二、职责单一

所谓 <strong>职责单一</strong> 包含三个指标:

  1. 是指程序中每个子系统、模块、包甚至是类所提供的服务都应在逻辑上具有很强的内聚性,他们只应服务于一个或者一类任务。

  2. 每个类或者模块都应该尽量独立的完成自己的功能,即不依赖于其它平行逻辑也可以做好自己的工作。

  3. 尽量和具体的业务隔离开(除了必须了解的那部分业务除外),切记逻辑上不相干的东西千万不要丢进来。

做到这些有很多好处,如果你面对的是一个按照这种原则来设计的系统,那么无论是在编写新功能还是优化旧有的代码,你的所有注意力都集中在这一个类或者包中,没有无关的东西来扰乱你的思考,你所有的注意力都集中在相关性上,其它的任何无关业务、逻辑都不入你的法眼,你会发现很容易保持一个干净的大脑环境,灵台清明,自然效率高高。

其次,由于模块的职责非常单一(很少甚至不关心平行模块的细节),类之间的相互依赖因而被弱化,模块的功能显得内聚和完整,代码重用神马的真是水到渠成……

还有另外一个好处,因为独立了、单一了、耦合降低了,你会有更大的自主权来进行合理的封装,你完全可以决定你的模块提供哪些服务,良好的api设计因而成为了可能。

觉得有道理?第三点来了。

三、尽量的封装细节

为何要 <strong>封装细节</strong> ?我们都知道代码一旦写出来,是要被其它人阅读或者作为代码基来使用的,而使用者首先需要了解你的代码,那他们会如何做呢?最直接的办法是浏览一遍你的包开放了哪些类,还有你的类放开了哪些方法。

因此,你的代码所暴露出来的细节越少,别人就越容易找到重点,也就是说,你的api设计的也越好用。

下面是我在 <a href="http://dtubest.github.com/Razor/">Razor</a> 中对参数绑定的设计:

binder

这个包中,所有的类都服务于参数绑定这个任务(职责单一原则),用来辅助从request中取出各种类型的参数。

它们可以应用于各种不同的参数类型的场景,但整个包只对外暴露了Binder一个类,其它所有的类都是包访问权限的,也就是说在包外,是不可见的,仅仅被限制在包的作用域里面来完成逻辑的细分, 而对外,你只要知道我这个Binder可以完成各种类型的参数绑定,怎么做的,you don't care!

相比于要你来区分场景然后选择合适的类,是不是一个简单的通用的方式适用与多个场景更加简单,更容易理解,这就是封装的力量。

ps:这个参数绑定模块的设计将处理不同类型的细节隐藏起来(封装细节原则),重复的逻辑全部放到了Binder中(无重复原则),只服务于参数绑定(职责单一原则),因为遵循了这些原则,他们很容易被移植到完全不同的场景中。具体的代码:<a href="https://github.com/dtubest/Razor/tree/master/src/main/java/com/me/web/servlet/binding">看这</a>

看到这里还有兴趣,很好。来接着看。

四、依赖简单化

先解释一下什么是 <strong>依赖简单化</strong> :

// 只是个示例, 请不要苛求语法细节
class Target {
    public String getProperty(){}
}

class B {
    public void logic(Target target) {
        String tempVariable = target.getProperty();
    }
}

class C {
    public void logic(String target) {
        String tempVariable = target;
    }
}

class Main {
    main() {
        Target target = new Target();
        B b = new B();
        C c = new C();

        b.logic(a);
        c.logic(target.getProperty());
    }
}

我们可以看到,B、C都依赖于目标对象Target的一个状态属性,但是我们说C的依赖更加的简单,先别问为什么,来看另外一个单元测试的例子:

// 同样不要关注细节,我希望只传达了重点所在
void unitTestForB() {
    Target target = new Target();
    B b = new B();

    assertThat( b.logic(target), "expected result");
}

void unitTestForC() {
    C c = new C();
    assertThat( c.logic("target value"), "expected result");
}

貌似还没有看出什么区别是吧?别急,一步步来,现在我们假设Target是这个样子的:

class Target {
    // constructor
    Target(Another1 a) {}
}

class Another1 {
    // constructor
    Another1(Another2 a) {}
}

class Another2 {
    // constructor
    Another2(Another3 a) {}
}

class Another3 {
    // constructor
    Another3(Another4 a) {}
}

class Another4 {
    // constructor
    Another4(Another5 a) {}
}

class Another5 {
    // constructor
    Another5(Another6 a) {
}
.....

现在你要怎样去测试类B呢?你突然发现测试真不是个好东西,因为测试本身开始变得比你要测试的对象还要复杂,你无法初始化你要测试的对象或者其他依赖,因为为测试准备的站桩太复杂了。

其实只要你把类写成类C那个样子将依赖解开,什么问题都没了。

也许有的人会说我可以mock啊,但是mock相比于依赖简单化,真的是更加优选的方式么,我想你有你自己的答案。

其实除了测试,在所有的业务代码中,简单化依赖后也是好处多多,试想一下,如果要从一个深层次的依赖中去挖出缺陷该有多么痛苦,如果你能解开类之间的复杂依赖,缺陷自然无出藏身。

好了,依赖简单化是原则,解依赖是手段。

接下来让我们来谈谈setter。

五、不要用setter来进行初始化

在具体开始之前,先来说说什么是一个好类,什么是一个坏类,还有不及格类:

我们知道,很多类都需要在投入使用之前进行初始化,他们的工作机制严重的依赖于初始化的情况。

在初始化正确的基础上,好类和坏类都可以正常工作,而那些不能好好工作的,我认为它是不及格的。

再来看,没有正确初始化的基础上,好类通常严重抗议(自举抛异常,或者有良好的默认设置),坏类则偷偷接受(默不作声,心思邪恶),不及格类嘛~~直接忽略。

好了,言归正传,setter方法其实从来都不是问题的根源所在,她是一个好姑娘,她们通常用在改变类的状态上,非常ok没任何问题,可是他们还可以用在初始化类的状态上,碰到一个好类,没有问题。要是碰到居心叵测的坏类,噩梦就开始了(想象力在哪?):

我们直接来看最糟糕的情况(setter碰上坏类 == 好姑娘碰上怀牛氓):

试想在你的某一个程序中使用了一个坏类,你用好姑娘来初始化它,好姑娘虽好,但你很粗心,你忘了初始化几个重要的属性,坏牛氓看在眼里,偷偷诡笑,默不作声,它不抛出异常,没有error也没有warn,只会得到和预期不同的结果。

在这种情况下,你会被程序不报任何异常,却不正确工作的诡异行为搞的晕头转向,怀牛氓不时瞎搞,各种临界问题不断出现,而由于坏牛氓偷偷隐瞒了问题所在,你完全无法定位问题出在哪里,怀牛氓如同鬼魅幽灵潜伏在你的code中,杀机四起。这时候,如果你同时没有布下单元测试这张天罗地网来捕获他们的话,那你就准备大巴的时间来debug吧……

这些个畸形的、没有正确初始化的、又不自举的坏类的对象,我把它称之为 <strong>魔化对象</strong>

setter本无罪,只是如果使用setter方法来初始化坏类,会容易导致问题的出现,坏类却偏偏又无法杜绝,因为有时候他是来自其他的类库(例如讨厌的commons-net),那如何解决这个问题呢?

孙猴子其实也是一个坏类,想想孙猴子是如何老老实实跟唐三儿去打下手的吧,唐三儿有温柔劝服嘛,NO,他用了紧箍咒,要踏上征程先带上紧箍咒,否则免谈。

聪明的你也许你已经想到了,那就是“构造方法”,一个类的旅程必须从这里开始,我们将初始化集中到构造方法中,每个从构造方法中构造出来的类都是ok的,这就避免了setter初始化中出现的所有问题(粗心、错误、遗漏、初始顺序)。对于其他你没办法改变的类,你也可以通过再包装一层来使用。

最后,写在最后

好不容易写到这里,差不多该结束这篇文章了,如果你有耐心看到这里还能记得前面,或者觉得津津有味,那真是太好了,可是我不好,我要吐了,写得好累,哈哈~——~或许这些东西你从没听过,或许你已经烂熟于心,有则改之,无则加勉吧。小弟水平有限,只能抛砖引玉,大侠们保持队形……

一点小小感悟,希望能听到大家不同的声音,如果大家捧场,以后再来个续集吧

转载于:https://my.oschina.net/dtkking/blog/113541

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值