BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

面向DSL设计API是否会形成语义的滥用?

| 作者 赵劼 关注 4 他的粉丝 发布于 2009年12月1日. 估计阅读时间: 11 分钟 | Google、Facebook、Pinterest、阿里、腾讯 等顶尖技术团队的上百个可供参考的架构实例!

本月初,博客园的老赵在其博客上发表了一篇文章,谈到了一种在他眼中兼顾性能和可读性的DSL,以此在ASP.NET MVC应用程序中构造URL。但也有人认为,这种构造方式违反了语言元素原本的语义,让人难以从签名中快速看出它的使用方法,因此是一种不可取的方式。

在使用ASP.NET MVC框架构建Web应用程序时,一个很常见的需求便是构造面向某个特定Action方法及参数的URL。老赵原本在他的MvcPatch项目(一个基于ASP.NET MVC进行改造以提高生产力的框架)中提供了一种基于表达式树的构造方式,但经过测试之后发现这种方式有很大的性能问题。因此老赵后来又提出一种流畅接口(Fluent Interface):

public static class UrlHelperExtensions
{
    public static ActionOf<TController> Of<TController>(this UrlHelper helper) where TController : new()
    {
        return new ActionOf<TController>(helper);
    }
}
 
public class ActionOf<TController>
{
    public string Action<T1, T2>(Func<TController, Func<T1, T2, ActionResult>> action, T1 arg1, T2 arg2) {...}
}

他认为,这是一种用于构造URL的DSL,可以这样使用:

Url.Of<HomeController>().Action(c => c.Post, blog, post)

以上这行代码表示的含义是“URL of HomeController’s action ‘Post’ with parameter ‘blog’ & post.”,它将会生成一个URL。针对这个URL的请求便会被转化为HomeController.Post方法的调用,并且提供DSL中所指定的blog和post的值作为参数。老赵认为,这个做法从性能上比原本基于表达式树的构造方式要高出许多,并且充分利用C#编译器的类型推导能力,拥有较好的可读性。而更关键的是,这种做法可以在编译期对URL构造进行静态检查。

不过在随后的评论中,网友装配脑袋提出,这个API不适合VB,不能算是真正优美的解决方案,而且更重要的是,语言元素应该用做原本职责所在的事情(即“委托”应该用于方法调用):

这种Url.Of<HomeController>().Action(c => c.Post, blog, post)的语法,真正给别人使用的时候,没有人会从方法签名或类的定义中快速看出它的使用方法。

另外你也应该能从我的代码里看出,在VB里,从方法名获得委托需要AddressOf运算符,你的代码就会变成Url.Of(Of HomeController).Action(Function(c)AddressOf c.Post, blog, post)。这其实代表了委托和方法语义上的差异。用这种语法,将造成滥用语义的倾向。

我觉得这条路彻底走歪了。从方法名获得委托,仅仅在用户明确知道是委托的上下文才有意义。你们不能滥用这个语法。

老赵则解释道:

其实我觉得API还是针对特定语言来说可能比较合适,否则的话,就算以前的Lambda表达式写法:Url.Action<HomeController>(c => c.Post(blog, post))放在VB里写起来还是麻烦。但是这种方式,其实微软本身也很推崇,在ASP.NET MVC 2里也有类似的API(不过不是做同样的事情)。

如果思考“非C#语言”语法的话,很多东西就不好办了,比如Moq。还比如F#的API也不会考虑C#的使用方式,语言特性不同么……我现在就好比是在为80%占有率的语言设计API,而只能忽视20%了,这是准备中的,除非有更好的设计,否则只能先满足这80%了……

另一网友Ivony则认为这似乎也不能算是走歪了:

委托不仅仅是方法的调用包装(透过委托可以调用方法),同样也可以是方法的信息包装(透过委托找到方法)。我们一般用前者,但不见得后者就歪了呀。

老赵又作了补充:

其实,是否可以从方法的签名中得出使用方式,这个我也不太在意。尤其是在视图中,我一般都把这些东西当作是DSL来看待,所以我会设计出“url of HomeController's action Post with...”这种语义的API。而使用的时候只要记住“用法”就行了,不关心“签名”究竟如何,就像使用不动点组合子来生成递归形式的HTML,从签名也实在看不出来。

的确,如果可以从签名看出来那自然最为理想,但是在DSL面前,这点还是让步吧,比如在FsTest里:

"foo" |> should equal "foo"
true |> should be True

这种东西,看should函数,be函数,not函数(的签名)……都是搞不懂该怎么用的,但就是为了让别人看明白写出来的代码,让别人知道该怎么写。

装配脑袋也进一步阐述了他眼中这种API的坏处:

用户不会想到这里是要写c => c.Detail,你没法限制用户第一层必须使用Lambda表达式,所以用户不可能思考到诸如Func<..,Func<>>是做这个目的的。

至于DSL,我觉得大部分尝试真的是玩具。论坛上好多人也热衷于折腾。但如果你想让你的类库有实用性,做API的时候正的必须要好好考虑所用语法的语义。

用奇技淫巧来写代码的时代已经过去了,C++的同僚们都开始清醒了……

Ivony认为,这种API虽然有问题,但其实也并非完全不可接受:

老实说我是没觉得这样优美的……不过我觉得这样也不算是特别的那啥。我明白你的意思了,如果这个东西作为API提供的确是不太合适的。的确是不能“自然”的直接悟出使用方式。

好吧,我的确没用API的高度去约束它。但实际上现在.NET的一些API的风格也在变,也有一些不是那么严谨的开始出现。我觉得这个度还是很难去把握。也同意你的观点。

接着,对于老赵眼中“较好的可读性”,装配脑袋也有自己的看法:

读着很通顺并不等于很容易地理解其行为。用户一般视方法为动作,每一步应该有每一步的语义。

Url.Of<???>...这一步你想让用户看出什么语义呢?将Url转化为???类型吗?如果想让这个稍微有点语义的话,我看只能设计成这样:Url.CreateForType<???>....

对此,老赵解释道:

我是看整句的:“Url of HomeController's action Post with paramters blog, post...” 其中只有“'s”和“with...”是我补充上去的,这也是我选择用Of作为方法名的原因。就像FsTest中:true |> should be True,它是作为一句话看代,而不是认为should作了一件事情,be又做了另一件事情。

还有比如Fluent NHibernate中:Reference<UserDetail>().Not.LazyLoad(),我觉得这也是在从整句进行考虑,而不是一个方法便是一个步骤。

最后,Ivony也系统阐述了他对于API设计的思路:

嗯,其实我觉得应该说只是有分歧没矛盾。

你要我说,我欣赏老赵这样的语法么?老实说我不欣赏,虽然部分方法虽然是我提出的,但这种语法要我接受我还需要一些时间。我在考虑这个方法的时候,也过分的追求Action(c.Post)( blog, post )而不是Action( c.Post, blog, post )的形式,因为我觉得前者才像是函数调用,后者看起来就不像是函数调用了,这样会造成一些阅读障碍。对前种形式过分的追求还使得我竟然没发现后种形式是成立的(真是个低级错误)。

至于老赵所用的这样两个方法的配合,你要我说,我真的说现阶段在API我还是不能完全认同。……但我也觉得,虽然现在还难以接受,却也真的不是有什么强有力的根据说这种方式有多么不可取。诚然,这种方法如果按照传统的从方法签名参数含义来阐述,基本上100个人99个不知道怎么用。不给Demo几乎没办法正确的理解使用方式。

但是尽管我觉得别扭,却也不得不承认这样的方案不错。我并没有放弃语义更明晰的方案的探索和寻找,却也真找不出什么特别有份量的理由否决这种方式。

……

所以这种事情是个见仁见智的问题,接受是需要一些时间的,老实说我是比较传统的程序员。我坦白第一次看到XElement的构造函数的时候,我都觉得不是很舒服。这种事情怎么说呢,可以说现在双方的观点我都认同,所以说我觉得没有矛盾,只有分歧。从这个角度来说,A是合理的,从另一个角度来说,B是合理的。不存在一个角度A和B存在非此即彼的关系。

API的设计是个永远的话题。尽管语言不断增强,但对于人们对于优秀API依旧进行着不断地追求。对于这个话题,您能给出自己的见解吗?

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

我觉得这个设计太牵强了 by 田 乐

1. C#的语法限制,这个Url.Of<HomeController>().Action(c => c.Post, blog, post)所谓的DSL读起来一点都不流畅,里面有太多奇怪的符号了。
2. 领域语言是自己设计的,不是英语,这个每次说DSL的时候都强调过。我看到文中的几种表达方式里面,老赵的方言无非是比较接近英语的顺序而已,没法说明人家的就不符合DSL设计的标准。
3. 所谓领域特定语言,那么你的领域是什么呢?你的领域就是MVC里面的Controller和http method这些么?它们能算作一种通用的领域么?对于程序员来说你写的这种所谓的领域语言就比另外几种方式容易理解么?领域语言是领域专家描述的语言,只要领域专家可以传递它的含义就可以了。现在的问题是,几种写法程序员都挺轻松能看懂,非程序员依然都看不懂。而且,那些对C#的某些古怪语法了解不多的初级程序员甚至不知道老赵写的这叫什么语法。这不就得不偿失了么?
4. 一个小的API的设计,是一个微观设计。它还没有到DSL这个级别……</homecontroller>

Re: 我觉得这个设计太牵强了 by Jeffrey Zhao

这里除了老赵提出的做法,没有其他的设计和做法吧……你是在说哪种啊?

入门门槛似乎有点儿高 by 侯 伯薇

想要使用这个框架来做开发,对开发者的要求似乎比较高,并非很容易入手啊。不知道这种感觉对不对。

Re: 我觉得这个设计太牵强了 by Wu Junyin

赞同。

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

4 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT