|
锁定老贴子 主题:脚本的未来
该帖已经被评为精华帖
|
|
|---|---|
| 作者 | 正文 |
|
时间:2005-11-19
脚本的未来
1. 编程模型的比较 下面是一些关于多态、灵活性的简单例子。假设我们有如下类型定义。 class Dog { run(); }; class Car { run(); }; class Chair{ stand(); }; dog, car, chair 分别是这些类型的实例 instance. 我们有个公用函数。 makeItRun( param){ param.run(); } 分别调用这些实例的Run方法 A. 解释脚本,如JavaScript, Python, Ruby, FP makeItRun( dog); // OK makeItRun( car); // Ok makeItRun( chair); // Runtime Error 特点: 根据符号、名字进行绑定, 运行期松耦合. 相当于语法直接支持Reflection。 B. 虚拟机编译语言,如 Java, C# makeItRun(param){ param.run(); } makeItRun( dog); // Compilation Error makeItRun( car); // Compilation Error makeItRun( chair); // Compilation Error 我们必须 让Dog 和 Car 实现相同的接口比如Runnable. interface Runnable{ run(); } 并且这么定义 makeItRun(Runnable param){ param.run(); } 这样. makeItRun( dog); // Ok makeItRun( car); // Ok makeItRun( chair); // Compilation Error 特点: 根据类型契约绑定. 编译期类型检查 C. Reflection API 我们可以使用 Reflection API 达到类似于解释脚本的效果. makeItRun(Object param){ methodInvoke(param, “run”); } makeItRun( dog); // Ok makeItRun( car); // Ok makeItRun( chair); // Runtime Error 特点: 语法本身不支持名称符号绑定,需要使用Reflection API, 这些API由Class 结构元数据支持. D. C++ Template <class T> makeItRun(T param){ param.run(); } makeItRun( dog); // Ok makeItRun( car); // Ok makeItRun( chair); // Compilation Error 特点: 编译期名称、符号绑定. Very cool. 注意: Java范型不支持这种方式。Java范型进行编译期类型检查。也是类型绑定的。C++ Template并不是真正的范型编程,只是一种代码生成机制。 总结: 运行期名称符号绑定,并不适合大规模复杂系统开发,因为没有编译期类型检查,不支持Java Interface那样的契约编程。 至于Ruby on Rails, 很像一个简单的代码生成器. 对于大型系统,比起运行期名字绑定,C++ Template 的编译期绑定也许更适合,至少有编译期检查。 解释脚本最适合哪些领域? 解释脚本是运行期名称绑定,松耦合。 什么系统需要这样的特性? 对了. SOA, Web Services, such buzzwords – 定位服务名称,调用注册的对象方法. 2. 脚本的可能未来 (1) 服务器段流程控制 JavaScript, Ruby, Python, FP 支持Continuation. 有时用作状态机流程控制。 Continuation 甚至比状态机. 可以跳到运行栈的任何一层。 (类似于Exception Throw/Handling). 个人观点,并不欣赏这种使用方法。因为web-based services应该设计为尽量无状态的。状态应该尽量保持在客户端,比如AJAX/Flex. 有人说,如果有人刷新了URL,客户端保存的所有的中间状态都会丢失,最好服务器段记录所有的中间状态,这样用户下次可以回到上次的断点。对于long-session 的应用,这是对的. 但是对于long-session 的应用来说,为了均衡负载,系统更应该被设计为无状态的。所以,使用脚本的Continuation特性,仍然没有太大的意义。 (2) Web Service 客户端 AJAX/Flex 也可以看作Web Service 客户端. 脚本调用Web Service,我欣赏这种用法。用脚本表达Web服务调用非常方便清楚,不需要Reflection API 和 Code 生成。 (3) 把Mobile Code 从 客户端迁移到 服务端 假设我们有如下的Web Service客户端脚本, If(service.do() == ok) { nextService.do(); nextService2.do(); }else{ backService.do(); backService2.do(); } 这通常意味着要访问三次Web Service Server. 每一次需要组装一次SOAP 或者XML-RPC 消息. 把整个Script一次发送过去如何? 服务器解释整个脚本,一次执行所有的服务请求,一次返回最终结果。 这涉及到安全问题。幸好脚本比机器码或者VM指令更容易管理和控制,解决这个问题应该不难。 (4) DSL, LOP ? Domain Specific Languages 如Business Rule, Workflow Definition 要求用户友好,更加像英语,而不是编程语言。 普通用户喜欢 关键字,而不是 API。 If a greater-than b then do … // keywords way If(greater(a, b)) { … // API way 脚本是更高级的语言,通常比编译语言更加友好。一些脚本(如 Lisp, Scheme, etc)可以定义更多的关键字。 声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
|
|
| 返回顶楼 | |
|
时间:2005-11-21
使用脚本的最大缺点是要编写脚本解释器,我宁愿去写一个可以实现同等抽象级别的OO架构也不会去写脚本解释器,为了解决一个领域的问题而不得不卷入另一个问题领域实在是很不划算。
|
|
| 返回顶楼 | |
|
时间:2005-11-24
脚本语言:makeItRun( dog); // Runtime Error
编译语言 :makeItRun( dog); // Compilation Error 明显脚本语言之能做小商贩,依赖于口头的合同,合同执行的好没事,持行到一般出错了怎么办? 编译语言作小生意,太麻烦,做大生意就不一样了,合同都经过律师们的详细检查,一部分很明显的漏洞都能被查出来,相对来说编译器这样的大律师行还是比较值得信赖的。大主顾也相对多些。 |
|
| 返回顶楼 | |
|
时间:2005-11-24
frankensteinlin 写道 脚本语言:makeItRun( dog); // Runtime Error
编译语言 :makeItRun( dog); // Compilation Error 明显脚本语言之能做小商贩,依赖于口头的合同,合同执行的好没事,持行到一般出错了怎么办? 编译语言作小生意,太麻烦,做大生意就不一样了,合同都经过律师们的详细检查,一部分很明显的漏洞都能被查出来,相对来说编译器这样的大律师行还是比较值得信赖的。大主顾也相对多些。 单元测试干什么用的呢? |
|
| 返回顶楼 | |
|
时间:2005-11-24
gigix 写道 frankensteinlin 写道 脚本语言:makeItRun( dog); // Runtime Error
编译语言 :makeItRun( dog); // Compilation Error 明显脚本语言之能做小商贩,依赖于口头的合同,合同执行的好没事,持行到一般出错了怎么办? 编译语言作小生意,太麻烦,做大生意就不一样了,合同都经过律师们的详细检查,一部分很明显的漏洞都能被查出来,相对来说编译器这样的大律师行还是比较值得信赖的。大主顾也相对多些。 单元测试干什么用的呢? 单元测试 和 类型检查 做的是 两回事,两个层面上的东西。 类型检查 属于基本的检查,一般用于检查 typo error,书写错误之类。很多IDE 也使用这个特性作为 输入提示。 单元测试 是更高一层次的检查,是为了检验 程序的逻辑 正确。 类型检查 一般干这种事情。 [code:1] long_calculation(){ startTime = currentTime; while (....) { calculate(); if( currentTime - startTime > 10 days) { // well, we run too long a time, better to stop to have a rest msg = "at " + current time " + we stopped "; log ( 1 / msg ); break; } } } [/code:1] 这里的 1 / msg 是一个笔误。 某一天,万里挑一的好日子,运行到这里的时候,会产生错误。 类型检查就是为了抓住这一类的低级错误。 注: 这也说明unit test没有做到家。任何分支都应该覆盖到的。 if( currentTime - startTime > 10 days) 如果那个分支被覆盖了。那就没有问题了。 T1 提醒:修改时钟,进行验证。 我猜想,假如测试逻辑的复杂度到了一定程度(现在来说基本不会,单元测试,单元两个字 保证了测试的简洁度),如何保证测试单元本身的逻辑正确性,这也许能带来测试概念变革 -- 元测试? ---- T1 忍不住,终于出手,:D 想不到本贴能引起T1的关注。 T1说: TypeCheck对于大型工程来说也不是必须的 这个世界上有一大群叫做C程序员的怪物,建造者计算机中的最关键的部分,但是成天和Char c=257,Void *Vp=(Void*)p;之类的东西打交道 whl 说: 这些属于类型强制转换了。 而且c是强类型的 T1说: C不是强类型的 你怎么知道一个Void* 一定是一个Char*?而不是一个某个Class 指针? C是弱类型的语言 Char c=257,这种表达式,在Java中绝对不允许出现的 whl 说: 所以,java能够很容易制造更大型的系统阿 你看,随便一个人都可以 制造出一大批java class类来 T1说: C构造的系统难道就没有Java大么? 这个世界上,C系统远远要比C++/Java总合还要多 whl 说: Java开发大型系统的开发效率高,还是 c的开发效率高? T1 说: 很难说Java会比C高 在没有类型的系统里面,那里的程序员,自有一套机制和方法去规避很多危险 whl 说: 这就是编程序的难度问题了 如果危险越小,那么意味着程序员的 水平不用那么高。 T1说: 你在动态语言里,在函数之前加上几个保护性代码,但是能带来更大的灵活性,何乐而不为 Guard Code总是要有的 whl 说: 保护性代码? 比如,precheck, post check ? try { } ? T1 说: 即时Java,里面你也要有 if (xxx==null) return false 这种guard code whl 说: 这属于逻辑检查阿 类型检查的作用,一般是为了自动发现一些低级错误 T1 说: 你那个 log(1/msg),即便msg是一个int,你也不能说,有了类型检查就万无一失了。 msg=0你怎么办? 你不能说这个逻辑分支,只要类型检查过了就完了 whl 说: 当然,我举的例子,只是一种假设的例子。类型检查,只能做到这个层次 另外,refactory也需要类型检查 T1: 运行期的出错未必就有编译期的可怕 那些Unit Test都没有覆盖到的逻辑分支,其逻辑错误的数量肯定要比类型错误多得多 whl 说: 这是两个层面。有了类型检查, 也少不了unit test。 没有类型检查,当然也可以工作。只是少了一些类型检查带来的便利。 比如,代码提示,refactory, 之类。 T1 说: 我的看法是,如果Unit Test覆盖到了某个逻辑分支,有些边界上的逻辑错可能逃过验证,比如说分母为0这种,但是类型错绝对是逃不过的。 whl 说: 就这个例子来说,是这样的 |
|
| 返回顶楼 | |
|
时间:2005-11-24
这个问题倒是值得说说的,就是静态类型和动态类型甚或无类型比起来,各有什么利弊。大家都承认,动态类型甚或无类型的语言,可以提供静态类型无法提供的灵活性,如果从代码的简洁、可复用、可扩展角度,动态类型语言占有几乎是压倒性的优势;但是另外一方面,静态类型系统提供的静态类型安全也是很有价值的工具,同时还有性能上的优势。
这两者恐怕不存在那个更好的问题 ── 在涉及如此多因素的情况下,一般意义上的“更好”,几乎可以肯定是不存在的。理解它们的利弊,意义在于扬长避短。拿大家比较熟悉的静态类型语言来说,显然可以从两个角度入手:一是试着弥补自己的短处,设法运用语言设施和设计技术来改善自己的表达能力;二是想法怎样将自身的优势发挥到最大,充分利用静态类型安全这个工具。 然而有意思的是,针对前一个角度,各式各样的讨论这里相当常见(充不充分另说),比如各种OO 原则啦、设计模式啦、容器啦、IOC啦、引入新的设施 AOP啦 …… 都是试图在静态类型的限制下给出更好的设计方案。但是后一个角度,几乎无人提及,大家都只是把它当作一道避免打字错误的简单防线 ── 然而它的价值应该不止如此的,有没有人对这个有兴趣? |
|
| 返回顶楼 | |
|
时间:2005-11-24
Elminster 写道 二是想法怎样将自身的优势发挥到最大,充分利用静态类型安全这个工具。
... 但是后一个角度,几乎无人提及,大家都只是把它当作一道避免打字错误的简单防线 ── 然而它的价值应该不止如此的,有没有人对这个有兴趣? 当然有兴趣。Elminster法师给讲一讲? 我能想到的,静态类型安全,可以用到 范型 中? 还有一个是 强契约。class.methodName (parameters ) 签名。 不过,现在的 thread local, reflection, dynamic proxy 都破坏了这个契约。显然,这种契约 是 阻碍了生产力的发展。 |
|
| 返回顶楼 | |
|
时间:2005-11-25
buaawhl 写道 ……
当然有兴趣。Elminster法师给讲一讲? 我能想到的,静态类型安全,可以用到 范型 中? 还有一个是 强契约。class.methodName (parameters ) 签名。 不过,现在的 thread local, reflection, dynamic proxy 都破坏了这个契约。显然,这种契约 是 阻碍了生产力的发展。 你说这种契约是“阻碍生产力的发展”(怎么听着这么别扭,让我想起了政治课 …… ),其实也难讲。方法签名也是静态类型安全的一部分,保证我们以“正确”地方式调用函数,打破它固然可以构造出更具扩展性、可易复用的框架,但同时也引入了更多出现难于发现的逻辑错误的可能性。特别的,如果一个错误要到运行时才能暴露出来,那就意味着发现这个错误必须依赖于当时的执行环境,错误无法重现的可能性大大增加。 不难看出,你的程序逻辑越复杂、变数越多、规模越大、变化越少,静态类型安全就越有利,反之,程序越“一根筋”、越小、变化越多,静态类型安全就越碍手碍脚。嗯嗯,对于 Javaeye 诸公比较熟悉的企业开发来说,我想多半是后一种情况。 |
|
| 返回顶楼 | |
|
时间:2005-11-25
嗯嗯,扯了半天废话,下面来点实际内容,我们来看怎么利用静态类型安全这个工具解决一个实际问题。这里我要引一个 Joel on Software 中的例子:
Joel 写道 一個例子
好了. 提到這個例子. 讓我們假裝你正在寫某種web應用程式, 因為這陣子小朋友似乎都流行寫這玩意. 現在有一種叫跨站腳本漏洞(Cross Site Scripting Vulnearability)的安全漏洞, 縮寫為XSS. 我在這裡不談細節: 你只需要知道在寫web應用程式時, 一定要小心絕不能把使用者填入表單的任何字串直接傳回來. 舉例來說, 如果你有一個網頁會讓使用者在編輯框輸入姓名, 傳送後就會跳到另一個寫著"你好啊, 張三!"(假設使用者的名字是張三)的網頁. 很好, 這就是個安全漏洞, 因為使用者可能不輸入"張三"而輸入某種奇怪的HTML及JavaScript, 這些奇怪的JavaScript就可能會做些低級事情, 比如讀出你寫的cookie內容轉送到壞人的壞網站去. 而這些低級事現在看起來就是你搞的鬼. 讓我們把程式用虛擬碼的方法寫出來. 想像以下的程式 [code:1] s = Request("name")[/code:1] 會由HTML表格讀取使用者輸入(一個POST的參數). 如果你曾經寫出下面的程式碼: [code:1] Write "你好, " & Request("name")[/code:1] 那你的網站已經有讓XSS攻擊的漏洞了. 光這樣就夠了. 你必須在複製回HTML之前先編碼才能避免這個漏洞. 所謂編碼就是把"換成&, 把>換成&, 如此類推. 所以 [code:1] Write "你好, " & Encode(Request("name"))[/code:1] 是絕對安全的. 所有來自使用者的字串都是不安全的. 任何不安全的字串都得先編碼後才能輸出. 我引用的这些文字来自一篇很棒的文章(Javaeye上似乎谁曾经转过?),全文在这里:http://chinesetrad.joelonsoftware.com/Articles/Wrong.html。 这是个很棒的例子,显然这是一个容易犯的错误,而且一旦你犯了这个错误,很难发现 —— 或许要等到你某人对你的网站展开攻击之后。对此,这个作者给出的解答是利用匈牙利命名法,让程序员只要扫一眼就知道自己有没有犯错 —— 这是个好主意,前面 T1 所说“在没有类型的系统里面,那里的程序员,自有一套机制和方法去规避很多危险”指的就是这些东西,有兴趣的朋友可以自己去看原文。但是这里我们有更好的办法:让编译器来代替我们做这个检查。 具体做法很简单,我们构造两个类型,safe_string 和 unsafe_string ,分别代表安全的字符串和可能不安全的字符串。它们的实现也都很简单,就是在基本的 string 外面套了一层什么也不做的壳,对于使用者来说,这两个类型和基本的 string 没什么区别。然后,我们规定: 1、所有类似 Write 这样的输出函数,只接受 safe_string 类型的参数; 2、所有类似 Request 这样读取输入的函数,返回类型都是 unsafe_string; 3、提供一个 Encode 函数,输入类型为 unsafe_string,返回类型为 safe_string,除此之外,不允许以任何其他方式构造 safe_string; 好了,只要我们在定义函数的时候满足上述规则,那么我们就可以证明,无论多么复杂的程序,只要它能够通过编译,都不可能存在上面例子中指出的错误。如果你试图把一个不安全的字符串赋给一个安全的字符串,编译器会告诉你类型不匹配;如果你试图输出一个不安全的字符串,编译器会告诉你函数参数类型错误。总之,你想犯错都不行。请注意,这里我们所得到的安全保证比其他任何保证都要可靠得多,因为它是由编译器的类型规则所确保的,既不需要依赖于个人的仔细和小心,也不需要担心什么代码执行路径的问题,而且是完全的一劳永逸,一旦正确定义了函数之后就再也不需要把这件事情放在脑子里面记着了。 很简单吧?这就是利用静态类型安全最基本的手法,要而言之,就是设法把逻辑错误转换成类型错误,保证有错误的代码不可能通过编译。 嗯嗯,说实话上面这个例子有点太过小儿科了,我估计有兴趣的朋友是不会过瘾的。好在这里有一个极其庞大复杂的例子,关于物理计算的,一定可以让朋友们吃饱。我们知道物理计算中,很重要的一个原则就是几乎所有的数值都有单位,而在复杂的计算中,人们太容易犯下弄错单位的事情了,把不能直接相加的两个物理量相加,然后赋给第三个不相干的量。这可能是因为少了一个括号,弄错了算符优先级,或是别的什么原因。无论哪种,最后结果都是莫名其妙的运算错误。下面这篇文章就展示了如何利用 C++ 的模板元编程技术构造一个复杂的体系,来保证物理计算中量纲的正确性。这很长也很复杂,有兴趣的人自己慢慢看吧: http://dalianit.com/edu/41543.html |
|
| 返回顶楼 | |
|
时间:2005-11-25
恩。c++的meta-programming确实是静态类型安全的。因为不等到运行类型匹配错误就会被找出来。
唯一的问题就是:这个错误定位有问题,往往不知所云。而meta-programming没有一个我们习惯的调试器来帮我们找到bug。 于是,查找一个meta-program的bug比找一个弱类型语言里面的bug还要费劲。 嘿嘿。 |
|
| 返回顶楼 | |










