惯例优于配置,配置优于实现

原创
2017/03/11 11:13
阅读数 1.5K

价值观阐明

简而言之,本文基于我们都认可这样的价值观为前提,即致力于编写人容易理解的代码

惯例优于配置

Ruby是一门优秀的编程语言,而且也处处体现了惯例优于配置的理念。可以通过简单的示例代码来体会这一约定。

如普通的操作:

def say_hello(name)
    # 先打声招呼
    puts "hello #{name}"
end

返回布尔类型的操作(方法名后面有个问号):

def more_money?()
    # 假设有点钱
    return true
end

一些危险的、可能会抛出异常的操作(方法名后面有叹号):

def borrow_me!(money)
    #  假设钱不够
    raise "Not enough momey!"
end

为了串联进来,假设有这么一个用户故事:你好某某人,有钱吗?借一点给我! 则对应的代码片段是:

say_hello '某某人'

borrow_me! 100 if more_money?

那么运行的效果类似如下:

hello 某某人
./test.rb:13:in `borrow_me!': Not enough momey! (RuntimeError)

配置优于实现

可以说,拥有惯例的开发团队,会有更高效的合作以及更为畅快的沟通。因为大家都能快速明白简明代码所体现的意图和目的,不存在混淆和错乱。
如果缺少开发语言的特性支持,或者所在的开发团队缺少约定编程的氛围,可以退而求其次,采用配置优于实现的做法。

关于配置的做法,在以使用注解的JAVA为例,可以看到其身影,如需要运行一组单元测试,可以这样:

@RunWith(Suite.class)  
    @Suite.SuiteClasses({    
        Test1.class,   
        Test2.class,  
        Test3.class,  

        TestSuite2.class  
    })  
    public class TestSuite1 {  
    }

相当于以下JUnit3中的实现代码:

public class TestSuite1 {  
    public static Test suite() {  
        TestSuite suite = new TestSuite("Test for package1");  

        suite.addTest(new JUnit4TestAdapter(Test1.class));  
        suite.addTest(new JUnit4TestAdapter(Test2.class));        
        suite.addTest(new JUnit4TestAdapter(Test3.class));  

        suite.addTest(new JUnit4TestAdapter(TestSuite2.class));  
        return suite;  
    }  
}

注:以上JAVA代码摘自 TestSuite的使用

通过配置,而是不编码实现,可以更容易传达所需要做的事情,而且配置的背后则是更为规范一致的约定,以及更为严格的检测,从而减少人为的失误的机会。最后,不仅是效率的回报,还是高质量上的获益。

开发-配置-使用 模式 (DEVELOP-CONFIG-USE PATTERN)

在过往的开发中,我也曾遇到类似的场景,就是活动的功能开发。但最后,我发现是可以把类似常用的活动功能开发抽离成可配置的形式,最后也印证了采用配置而不是实现编程,可取得效率上的提升以及高质量的回报。

在曾经关于设计模式的分享,我也把这一模块进行了总结,如下:

  • 问题:对已有的功能模块,仍然通过编码实现来重复调用。
  • 解决方案:一次开发后,通过配置而不是代码实现,来使用或定制已有且可重用的功能。
  • 效果:最大限度减少人为编码的错误,并统一规范的检测、验证、解析过程。
  • 已知应用:nginx配置、Yii表单验证规则

向前一步,声明式编程

配置式的编程固然是好,但视不同的上下文而定,有时还可以再进一步,进行声明式编程的范畴。

考虑以下表单,

对表单数据进行验证的JavaScript代码片段如下:

validator.init([{
        dom: iptMobileDom,
        rules: [{
            strategy: 'isNotEmpty',
            errorMsg: '手机号码不能为空'
        }, {
            strategy: 'isMobile',
            errorMsg: '手机号码格式错误'
        }]
    }, {
        dom: iptAuthcodeDom,
        rules: [{
            strategy: 'isNotEmpty',
            errorMsg: '验证码不能为空'
        }]
    }]);

上面的代码不难理解,作用是对手机号和验证码进行验证,并且通过配置规则可以快速实现对表单的验证。但这样依然有两个缺点:一是具体的规则 不能直观说明需要验证的内容,二是当其他场景需要进行类似验证时需要重复编写相同的规则。

为此,我们应该采用声明式编程,即:我们应该告诉代码(同时传达给我们的同伴),我们需要验证什么,而不是我们怎么验证。

首先,可以定义一个规则集合常量:

const rules = {
          mobile : {
              isNotEmpty : {
                strategy : 'isNotEmpty',
                errorMsg : '手机号码不能为空'
              },
              format : {
                strategy : 'isMobile',
                errorMsg : '手机号码格式错误'
              }
          },
          authcode : {
              isNotEmpty : {
                strategy : 'isNotEmpty',
                errorMsg : '验证码不能为空'
              }
          }
      };

接着,使用上面元规则进行自由地组合使用,声明需要验证的内容。调整后的代码为:

validator.init([
        {
            dom : iptMobileDom,
            rules : [rules.mobile.isNotEmpty, rules.mobile.format]
        },
        {
            dom : iptAuthcodeDom,
            rules : [rules.authcode.isNotEmpty]
        }
    ]);

这样之后,再来看下其他类似的场景,我们即使不细看扩展丰富后的规则,也可以很容易理解明白下面代码的意图。

validator.init([
        {
            dom : iptPasswordDom,
            rules : [rules.password.isNotEmpty, rules.password.format]
        },
        {
            dom : iptMobileDom,
            rules : [rules.mobile.isNotEmpty, rules.mobile.format]
        },
        {
            dom : iptAuthcodeDom,
            rules : [rules.authcode.isNotEmpty]
        }
    ]);

在上面常见的表单验证的场景中,可以发现其中一些微妙的关系。需要待验证的DOM节点是可变的,因为不同的场景界面会有不同的class,同时需要进行验证的条件也是可变的,即会存在组合的情况。但是各个规则条件又是可以共用的,最后每一条规则条件都是可重用的一条元规则。

故此,把不变的元规则抽取成规则集合,既方便重用,又能增进开发人员之间跨时空的理解。

稍微一转化,便可得到以下的设计模型:

小结:三种视角的回顾

关于三种视角,即:概念视角、规约视角和实现视角。出自的书籍截图如下:

对于概念视角,我们应该尽可能使用一句代码来表达完整的概念。这是作为电高层的意图说明,应该做到简明扼要、流畅达意。

正如前面借钱的用户故事中,我们可以稍微这样转换成更自然的语言(当然,这里语法上是错误的):

# 某某人你好,有钱吗?借我100块!
say_hello '某某人' , borrow_me! 100 if more_money?

为什么说惯例优于配置,配置优于实现呢?

因为对于要做一件事件,惯例可以说是不用写任何代码就能轻松实现的;配置是需要简单地写一些任何语言都能识别的普通字符就可以了;而实现则是需要根据不同的编程语言而进行具体的开发。

结合上面的三种视角,实现则是对应了实现视角。再说得通俗一点(但不一定是对的),惯例为上策,配置为中策,实现为下策。

展开阅读全文
打赏
1
16 收藏
分享
加载中
更多评论
打赏
0 评论
16 收藏
1
分享
返回顶部
顶部