Category Archives: 技术

说个react-redux生产环境追踪action,state流的黑魔法

用react,react-redux开发前端的时候,在开发环境,我们会enable redux dev tool extension,方便我们在线调试问题,redux这种单向数据流,状态驱动的好处就体现的淋漓尽致了。但是如果在生产环境,通常都会disable掉这样的浏览器扩展,一旦有了问题,我们很难在线去追踪action流和store的变化,有时候debug一个问题还挺痛苦的。这里讲个黑魔法,关键点就是在生产环境取得store对象的引用。当然可以在代码中直接在window对象上设置store对象,这是最简单的方法,但是这样等于把store对象直接暴露给全世界,一般开发过程都是不允许的。如果不设置全局的store对象,那我们能不能从React的$r对象上索引到store呢?打开页面,尝试在Chrome console直接输入 $r.store,它会报告store是undefined。

你需要额外的几个步骤,下面操作基于Chrome,React Developer Tools extension,不管是不是生产环境,React扩展总是可用的。

  1. 打开React Developer Tools extension,选中store节点

2. 这个时候在$r.store就是可用的了,打开console,输入,

3. 随便浏览下页面,每次store的变化都会在console里打印出来了,你还可以手工dispatch action。

4. 如果你还想把action流也打印出来,就需要加一个很小的reducer在你的code里面,像下面的actionStream.js一样。熟悉redux的话,这是很简单直接的事情,就是在redux上的store上加了一个状态去记住每一次reducer调用时候的action参数。

//actionStream.js
export default function actionStream(state, action) {
    return action;
}
...
//combineReducers.js
import action from 'actionStream.js';
... //import other reducers
export combineReducers({...,action,...});

然后,subscribe的语句变成下面就好,

$r.store.subscribe(()=>{
    var state=$r.store.getState();
    console.log(state.action);
    console.log(state)
});

仅仅追求能够工作的代码是有害的

作为一个老程序员,我也曾经询问抑或被问,“好的代码是个什么样子的?”

回想刚刚参加工作的时候,受限于个人的经验与天赋,绞尽脑汁的写出只是可以正确工作的代码已然是一件不易的事情,虽然如此,每当完成任务后,内心仍然一直念念地要去把它写的更好,即使那时候还没有明白重构究竟是怎么一回事,就是凭着一股子热情一遍遍的重写,从变量名,从语句顺序,从结构安排,从括号与每一行代码的缩进,精益求精,每当看到差的代码,总想去把它写的更好。

随着经验与教训的增多,自己写的第一版代码的质量也逐渐提高,对于各种细节的考虑也愈加周到。然而,不知道从什么时候开始,却心安理得地接受了这样一种观点,

能够工作的代码就是好代码

再也不会费尽心思的对能够工作的代码进行重构,同时,有无数的前辈和同事以及自己找了各种理由来赞成这种观点,从软件工程理论,从开发进度限制,从人员水平高低,不一而足。慢慢地大家看到低质量的代码,第一反应是去为这段烂代码找各种借口,我们容忍越来越多的低质量代码,更可怕的是我们正在容忍它逐渐侵蚀什么是好代码的判断力。

这是有害的,至少程序员来说。写代码就是我们的手艺,这不过是我们在手艺慢慢成熟的同时,内心正在失去热爱,失去追求卓越的动力所找的借口。这一现象在各行各业是相当普遍的,从中等水平到优秀,从优秀到卓越,每一步都要比刚开始的时候付出多的多的努力,然而效果却并不来的那么明显,至少从行外人的视角看来。因为艰苦,所以每个人都容易原谅自己在这条路上的徘徊止步,何况对于程序员的行业来说,这是个相当不错的理由——毕竟代码在正常的工作,不是吗?然而对于其他专业行业来说——医生,律师,会计师,飞行员……——分毫的差距,便是生存与淘汰。

回想当初入行时,当别人问“程序员的工作有什么好的时候?”,我们自豪的回答,“与机器对话要简单的多……我喜欢这种掌控的感觉……代码从不说谎”。写代码,我们是专业的。我们看到优秀的代码,两眼放光,我们为自己写出的优秀代码鼓掌,我们赞赏自己的技艺。然而,日复一日,年复一年,如今我们如何成为了许多人口中的熟练工,我们为何不再精益求精地打磨自己的技艺,不再追求优秀。突然间,许多同仁们恐慌,焦虑,到处充斥着“程序员到了35岁该何去何从?”,“程序员到了40岁还继续写代码是不是一种悲哀?”,“35岁老程序员被裁该何去何从?”的话题——可怕吗?在我们心智应该在人生顶峰的时候,我们甚至失去了凭着这手本事安身立命的信心,这在许多其他专业行业是罕见的现象,你可能想象不到这也许是从多年以前我们接受那个看似无害的观点——能够工作的代码就是好代码——开始的。

我想到Linus在Ted访谈时,提到好的代码的时候那种热情与兴奋,这段代码是最普通的一段C代码,完成最普通的功能,甚至我们每一个人都写过很多遍——移除链表的一个节点。版本一是合格的,但希望对于版本二你能闻到卓越代码的味道,那么我们至少还能分辨在这个行当里,什么是优秀,我们距离优秀还有多远。知道了不足,我们便可以奋起追赶,去重拾攀登卓越的信心。

版本一

    void remove_list_entry(entry)
    {
        prev = NULL;
        walk = head;
        while (walk != entry){
            prev = walk;
            walk = walk->next;
        }
        if(!prev)
            head = entry->next;
        else
            prev->next = entry->next;
    }

版本二

    void remove_list_entry(entry)
    {
        indirect = &head;
        
        while ((*indirect) != entry)
            indirect = &(*indirect)->next;
        
        *indirect = entry->next
    }

一次中大型产品web前端的技术选型过程

背景:老产品的基于ExtJS的前端需要配合server端微服务改造重新实现。

先说个人观点,非常喜欢 Vue,也想在这次技术选型中选择它,然而发现要说服各种技术人员,从开发人员到管理层,都有不小的阻力——不是阻挠的阻力——是在对于几个框架都比较陌生的情况下一种选择困难。对于大型产品开发来说——上百人月的开发周期——管理层还是以规避风险为优先,Google和Facebook的背景对于这些久不在开发一线的决策者们还是有很大影响的。

开发人员的观点

1.  不大愿意学习新的框架,并不是排斥,只是热情不高,这部分人一般比较资深,然后在传统前端开发领域积累了非常多的经验,这些人其实已经认识到,框架本身并不能让真正难的事情变得简单,也不能帮助做出漂亮的UI和交互体验,他们也不会迷信Google和Facebook的背景,有些人才刚刚爬出GWT的大坑。对于这些开发者来说,在选用新技术决策中是中立的,vuejs,Angular 2,React,无论选哪个他们都不会太反对,vuejs对于这批人是有着很大吸引力的,因为vuejs本身渐进式改造的理念对于他们来说是很容易上手的,不需要构建工具链虽说不算什么优势,但是对于传统的开发者来说,习惯过渡得非常自然,引入vue.min.js 即可以开始尝试开发,老开发者是比较拥护的,作为比较,如果使用Angular 2,在没有angular-cli或者其他skeleton的情况下,“Hello Angular”要比“Hello Vue”要来的难得多!这不仅仅是步骤的繁琐,甚至,他必须先去学习一门新的语言Typescript,去配置好几个文件。

2.   赞成Angular 2的,这部分人中,大部分是在另一个产品开发的时候,在Angular 1上积累了经验,这部分人里面对于Angular 2的学习曲线和Typescript也是颇有微词的。一部分是原来server端的人员,他们对于前端开发没有太多的经验,他们非常喜欢Typescript的类型特性,这部分开发者其实对前端热情不高,也不会去研究框架的实现,也不会研究ES5,ES6究竟是如何一回事,他们喜欢的是读完Hero tutorial然后按照套路,能够比较快而迅速的完成任务。

3.  赞成React的,这部分人其实有很多是开源社区的强烈拥护者,他们热爱开源,喜欢学习新东西,喜欢整合各种开源的好的实践到开发中来,他们很多时候喜欢稍微激进的技术架构选型,我看到一个方案是React + GraphQL + nodejs + RethinkDB的全栈javascript实现。

管理层的观点

1.  vuejs不能选,尽管vuejs在开源社区已经有着不算小的影响力,但相比较于React和Angular来说,背景和影响力现在还是不能比的,管理层们不愿担当这种决策风险。

2. 不反对React,因为React的影响无论在社区还是实践中实在是太好了,国内国外,随处可见褒扬的评论文章。然后很多人就忽略了React实际上只是负责了展示层,React一般需要比较多的整合努力,需要资深人员来挑选其他的库来配合使用。

3. 倾向于Angular 2,是因为它是大而全的一站式框架,对于整体代码质量控制和团队协作开发过程非常有帮助。

在技术选型初期,做原型的过程中发现这么几个Angular的问题,

1. Angular 2 deprecated了replace的选项,导致angular组件的Host元素始终显示在dom树上,绝大多数情况问题不大,但有时候会造成布局的破坏,比如有时候通过height/min-height:100%来控制高度,因为中间selector的存在导致布局的破坏,虽然可以添加@HostBinding去设置host元素的CSS属性,但这无疑是额外的负担。而且在开发工具上看DOM树总是有点奇怪的,中间插入了层层的各种selector元素。在开发中不得已开发了removeHost的attribute directive。

2. Angular 2如何方便与传统的js库一起合作,因为Angular 2从设计上就是排他的,对于企图渐进的逐步改造老系统的开发,有着比较大的影响。实际开发中有一点比较奇怪,我们的module bootstrap component是MainLayoutComponent,它有着下面的template,

 <main-layout>
    <app-header></app-header>
    <app-navigator></app-navigator>
    <route-outlet></route-outlet>
    <app-sidebar></app-sidebar>
    <app-footer></app-footer>
</main-layout>

这些组件模板的定义,我是从adminlte中抽取出来的。对于vuejs,我像传统的使用一样直接在页面引入adminlte.css和adminlte.js,没有任何问题,但是对于angular2来说我还必须把布局的初始化过程从adminlte.js里面抽取出来,写在MainLayoutComponent组件的onViewInited回调方法里,因为如果直接在页面引入,当初始化执行的时候component的template并没有开始编译和mount到dom树上,adminlte的初始化的部分代码是无效的,比如如果某些事件绑定不是基于全局捕捉的模式就会失效,

   $('#toggleBtn').click(function(){
   });

只有改写成下面的形式才是有效的,

   $(document).click('#toogleBtn',function(){
   });

Angular 2和vuejs的模板绑定到dom的时机应该是不同的,这可能会影响到一些前端库的使用。

3. 如何方便的从UI定义的元数据中compose组件的template,这一点vuejs同样也不能解决,因为他们都不是基于string template的,Angular 4的 NgComponentOutlet有可能帮助一点点。

4. 前几天演示研究原型的时候,好几个开发问我关于Angular 2的组件定义,在export class … 前面加个@Component(…)的函数调用是什么意思。

 @Component(...) export class XxxComponent

想讲清楚这个,又牵涉到ES6的DecoratorDecorator Factory,感觉Angualr 2使用的语法比较的超前,大家过渡得很不适应。当然如果是照着Hero的tutorial大家一般都没有问题,然而如果是规划一个产品的road map,那么必须要有人对其研究的非常透彻来应对未来可能出现的各种技术难题。

目前看来,基本决定使用Angular 2了,对于大型产品的跨团队开发来说,有巨头背书的一站式的解决方案是有决定性影响的,有点遗憾,这次没有说服其他人来选择Vue,还是自己的影响力不够。

Laravel 5.3前端ajax请求,后端丢失session的问题

微信的h5产品,使用React+Laravel 5.3,一个奇怪的现象是只有正常的http请求,在server端可以得到session数据,所有的fetch api调用都无法得到session数据,导致所有的api调用返回401,需要授权。最初后端认为是前端在fetch调用的时候没有加上credentials参数,导致laravel_session cookie没有发送。

credentials:'same-origin'

后来前端加上了credentials以后,从微信开发工具里看到,cookie确实发送了,而且包含了laravel_session的cookie,然而后端始终拿不到session数据。搜索了google,stackoverflow和laravel社区,大家有很多类似的情况,但都没结果,有的说在ajax并发请求频繁的时候会出现这种情况,有的说Session Facade不是在所有的地方都可以用,有的说是Laravel 4.x的一个bug,试遍了各种办法,也没解决,最后还是在入口处打了两种请求的callstack,果然发现了不同,正常的请求比api请求多经过了几个middleware的处理,在Http/Kernel.php里为两种请求配置了不同的middleware。

protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'throttle:60,1',
            'bindings',
        ],
    ];

重点就是web经过的EncryptCookies middleware去解密cookie内容,而默认的api是不经过这个步骤的,因为laravel默认api请求是无状态的,需要每一次请求在header携带必要的token完成authentication,知道了原因,改起来很简单,为api请求也加上EncryptCookies的middleware。

protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            \App\Http\Middleware\EncryptCookies::class,
            'throttle:60,1',
            'bindings',
        ],
    ];

面试杂感

今天又有新人来公司面试,我看这个年轻人背景本科是西安交大,硕士是北航软件学院的,私下以为是个很不错的小伙子,稍加培养以后可以成为组里的生力军的。没想到,面下来,真的失望,我不敢相信交大和北航出来的学生是这种水平,就详细地问了问,他自己不好意思地说,虽然简历上写的是交大和北航,但交大是西安交大城市学院,一个三本学院,北航是软件学院在职读的。目前他已经取得了硕士学位,我更诧异现在软件学院办的这么滥了,教学水平是如何控制的,我让他找出无重复无序数组里面的一个值,找到了就返回序号,没找到返回-1,像下面这样,

int search(int[] data, int target);

我在白板上把原型写出来,他居然写不出一行代码,连for循环都写不出来,这就绝不是紧张可以解释的了。难道经过接近6年的科班训练,连这种程序都写不出来的话,究竟是怎么一回事?如果说跟个人有很大关系,但是毕竟北航授予了硕士学位,只能说北航的软件学院的教学质量堪忧,在砸北航的招牌,说实话,还不如有些培训结构出来的小伙儿。

技术学习实践的“快“与“慢“

刚刚看完了Spring 4.3.4和Spring Boot 1.4.2的Reference guide,有点感念,想就技术学习的快与慢谈一些自己的感受。

坦白讲,我对新技术学习不算快,一方面是自己不够聪明,缺乏天赋,另一方面是自己多年养成的学习习惯使然。现在回想起来,当年学习C++的时候,第一本书是课程教材,用的清华大学钱能的那本《C++程序设计教程》,这本作为教材挺好的,现在虽然已经记不太清当年的学习过程,总归印象还是不错的,还是打下了比较坚实的基础,最起码C++期末考试考的还不错。后来大家一致推荐的《C++ Primer》,内容组织合理,讲解条理清楚,贴近规范和最佳实践,然而偏偏我当时不知道钻了什么牛角尖,一定要固执地坚持看C++爹的《C++程序设计语言》,当时看的是北京大学的裘宗燕老师翻译的, 回忆起来,有一些翻译的梗今天看来还是挺有意思的,记得当时大家都说裘老师一定是看花眼了,把”inline”看成“online”了,所以“内联“都翻译成”在线”,结果裘老师还在各个地方和大家争论——为什么翻译成“在线“,究竟是什么原因现在也没有定论,如今没人再寻根问底,都淹没在历史里了,目前我一直看的是第四版的英文版,说回书本身,这本书的特点就是信息密度非常大,很多非常重要的地方一打眼就错过去了,虽然我已经看的很慢了,但直到今天,有时因为工作需要查找某些知识点,翻到某些页还像第一次看到一样,我发现在xx语言发明者写的书里面都是这种风格,因为他们太熟悉了,所以知识点经常会出现很多的交叉索引,而大多数学习者习惯于线性思维,经常会陷入“这个是新的么?我学过了吗?”的混乱。后来用Scala的时候又再次感到了类似的体验——谈到语法,Scala不亏是不服C++的主儿——后话暂且不表,以后专门写Scala。

《C++程序设计语言》这本书的阅读体验从某种程度上很符合我的学习习惯,我一直没有买《C++ Primer》,而是买了它的习题集全部做了一遍,所以我跟别人讨论《C++ Primer》的时候,一般会提到“不要简单的把它当资料书,前辈们这么说是因为他们已经首先扎扎实实的读过了一遍,甚至每一道习题都做了一遍,如果要用它学习C++,请花大块的时间先认认真真的读一遍”。这种习惯一直贯穿着我学习技术的方方面面,很多人也称之为“笨”或者“难”的方法,然而很多技术书的“hard way”系列质量都很高。后来因为工作需要,转到Java领域,我读的第一本书也不是大家一直推崇的《Java编程思想》,而是高司令的《Java编程语言》,直到今天,关于Java,这本和《Effective Java》,《Java并发编程实践》仍然是我翻得最多的三本书,而且我发现自己还是能在书上不断得学到新的东西,比如这本书的讲解Serializable的部分,你知道你可以通过定义private static final ObjectStreamField[] serialPersistentFields 去制定序列化字段的名字和类型么?大家不要误会,这不是茴香豆有几种写法的八股,而是常看常新的一种体验。

后来工作中需要,接触了Spring框架,我记得很清楚,花了几周的时间一点一点把reference完完整整的看了一遍,这以后,再有新的版本升级,我每次都大概只用半天到一天的时间就能把新的reference全部过一遍了,这其中的“慢”与“快”很有体验。相反,有些同事从来没有认真完完整整的看过一遍,都是工作中用到哪点看哪点,到现在也没有对Spring形成一个相对完整的认识与理解,盲目推崇面向Google和StackOverflow编程。其结果就是经常出了一点非常specific的问题,就漫无目的的东找西搜,即使源码在手,也不知从何下手,其根源在于根本没有对Spring的整体设计与习惯用法有着清晰的理解。

再后来,因为工作中要设计一种DSL,加上通过JavaEye接触到Ruby,我也开始了Ruby的学习,惯性使然,我还是选择了一个相对“慢”和“难”的路,一直在用的两本书是Ruby之父写的《Ruby程序设计语言》和《Ruby元编程》,这两本的特点也是信息量非常密集,需要慢慢地看,时时的理解,每次我尝试着加快速度的结果就是一遍又一遍的回头去看,就其效果而言还不如慢下来。

虽然我自己习惯于这样的“慢”,然而在实际中也不总是使用“慢”方法,比如我平时开发一些web应用,虽然我对Spring MVC很熟,Thymeleaf模板也很好用,但总还是比不上用PHP Laravel或者Ruby on Rails来开发来得更方便,更迅速,而且即改即测。

“快”与“慢”总是相对的,也跟每个人的习惯有关,而习惯这个东西是比较难改的,但是要想成为一个好的程序员,我想我们总是还要“慢”下来,或早或晚。

基于Java的轻量级工作流引擎实现

基于Java的一个简单的工作流引擎实现,用Java 8自带的Javascript nashorn引擎作为工作流生命周期任务扩展定制。代码已经比较完备,nashorn为Java对象的互操作提供了很多便利,使得Java和Javascript几乎可以无缝的一起工作。

    1. 属性访问。如果Java对象实现了Map<String,Object>接口,当使用obj.foo = bar时,nashorn会调用obj.put(“foo”,bar),如果是一个普通的POJO,nashorn会用反射的方法去调用getFoo/setFoo,这个地方需要注意的是,如果缺乏相应的getXXX/setXXX方法,它不会报错抛异常,只是就像没调用一样,所以你会发现执行中出现奇奇怪怪的行为。
    2. 方法调用。一个注入Javascript的Java对象上的方法调用跟在Java代码里的方法调用没有区别,如果你想让这个Java对象表现的像一个函数,只要这个Java类继承AbstractJSObject或者直接实现JSObject接口,尤其是实现call(Object thiz, Object[] args)isFunction方法,那么这个类的实例就可以像一个函数一样在Javascript里调用了,更简单的方法是使用Java 8引入的functional接口Function<R,T>::表达式,我们只要对java对象xxx或者类的method方法做个正确的强制转型,(Function<R,T>)xxx::method,然后把转型结果注入引擎,就可像函数调用一样使用这个Java对象了。有了这样的支持,我们就很容易注入自己java业务对象,给客户扩展定制自己的业务,比如注入mailer,给客户用脚本发邮件,多数情况下已有的Java类稍微修改就可以在Javascript中使用了。

工作流本身的定义使用了Spring Data JPA存储在数据库中,Github 自取。

领域驱动设计实践中的几点思考

1.  要尽量建立充血的entity/aggregate模型,只有不适合放在entity/aggregate的行为才需要提升到Service层去实现,一般这是由DDD的一些设计准则导致的。(1) 要保持小的aggregate设计,保证事务执行的粒度 (2)要避免从aggregate直接navigate到其他相关aggregate的集合,即使Lazy Loading DDD也不提倡,而这样导致一些业务方法在没有repository/service注入的情况下没办法实现相关业务逻辑。有人认为这点多少是与传统的面向对象设计有冲突的,然而确实在实践中大多数情况下是应该遵守的,比如Category.getArticles(), 有人认为这是Category应该提供的行为接口,然而在DDD设计中是极力反对Category提供getArticles的访问方法的,反而用ArticleRepository.findByCategory来实现,这样的访问是可控并且有意义的,比如加入分页参数对于repository的query方法来说是有意义的,反之,如果在Category.getArticles加入分页信息是有点滑稽的,底层的分页概念侵入到了业务模型,如果不加,那么当某个category分类下面包含很多acticles时,Category.getArticles这么一个普通的可能是业务代码无意间的调用就会造成极大的系统内存抖动。

2.  Service层应该是无状态的,这里有个有趣的问题国内外的架构师一直在争论——Service层方法应该接受aggregate的实例,还是相关aggregate对应的ID?如果是前者,那么在application层应该负责保证生成正确的aggregate实例,这可能会带来一些不必要的查询,因为有的方法只需要ID即可,不需要实例。有些架构师会设计一个ServiceFacade的层,来处理DTO层传过来的包括ID等各种简单类型的String,Long值,而Service层只针对aggregate实例来做业务逻辑的计算。对我而言,我更喜欢这种设计,即service层针对实例来实现方法,虽然会带来一些潜在的不必要的查询,但是当entity/aggregate是充血建模的时候,绝大多数场景仅仅ID都是不够的,当然这也取决于你的service方法是否需要。总体来说在这一点上DDD的原则和传统的OOP设计是有部分冲突的。DDD也认识到没有类型信息的简单类型Long,String是非常容易误用和不友好的,所以一个常用的技巧就是DDD用一个值对象(Value Object)来包装ID,即使这个ID就是一个Long类型,比如用class ArticleId来包装article的ID,这样在service层统一使用aggregate的ID而不是实例,由service方法来决定是否要构造这个实例,这也是一种非常常见的方案。

Web应用开发架构的演变

拓荒时期,1995~1998

彼时开发主要以CS模式占主导地位,每一个产品的Client端和Server端的通信都是私有的二进制协议或者基于某种文本描述的协议,例如SOAP( 1998 ),XML-RPC,或者DCOM,CORBA类似远程过程调用框架。其中远程过程调用的概念出现的非常早,而且这一概念一直延续到今天,目前看来大家在分布式系统中更倾向于这种通信方式,比如facebook的Thrift。Google的ProtocolBuffer和Apache的Avro更像一种数据打包方案。Java语言从一开始就集成了对CORBA的支持和对RPC的支持——RMI。拉回web开发来说,1994年网景浏览器发布(基于http 0.9 1991年发布),给大家无限可能的想象,把整个世界装在一个浏览器窗口,而http协议本身在1996年才完成了1.0的草案。那时候BS模式的开发绝对是超级激进的方式,绝大多数的网站都是以展现静态内容为主,没有web应用( Web Application )的概念,website这个词使用了很长一段时间。老程序员们对当年手写一个静态页面100块的价格应该比较能感受到技术的价值。接下来,CGI( Common Gateway Interface )方案登上web后台的开发舞台,作为web后台开发的基础,这种架构一直延续到今天,并引发很多响应的变种Fast-CGI,WSGI,Rack,其本质就是在server端设置一些环境变量然后启动相应的脚本解析器执行内嵌的脚本,然后输出到浏览器端,好处是传统程序员擅长并且非常容易接受这种转变模式。

这个时候后端的代码呈现出来的特征很像后期jQuery繁荣时的一样,以页面为业务组织单位,哪里需要动态内容就插入脚本标签,html标签和业务脚本代码完全混在一起。

这个阶段一些奠定web基石的基础性软件apache http server ( 1995 ),PHP ( 1995 ),Perl 5 ( 1995 ),Java( 1995 )发展繁荣,搭配mysql作为后端数据库的web站点呈现爆发式的增长。

J2EE的统治时期,1999~2005

经历了web开发的无序混乱的拓荒时期,大家对BS这种模式已经不再有怀疑,但对能做到什么程度,是CS模式的部分补充还是将会慢慢的占据统治地位,这个时期是不存在大规模的现代意义上面对终端用户的的互联网业务的。广大开发者和企业一方面对BS的模式感到兴奋,一方面都在翘首盼望着一个杀手级企业应用开发框架来证明BS架构才是未来。1999年底,J2EE横空出世,2是跟随Java的大版本号Java 1.2,从这个版本开始,Sun一口气推出了J2SE,J2EE,J2ME三大平台,野心很大,也很有前瞻性,有这样的观点——Android也不过是J2ME的在一个移动平台的『借尸还魂』。当时最引人瞩目的无疑是J2EE平台。它的出现让整个企业级开发市场沸腾了,规范覆盖了企业级开发需要的方方面面,老程序员应该对当时Sun官方给出的几个示范应用,PetStore,Duke银行应该还有印象。

J2EE

J2EE规范把大量的底层支撑功能从业务实现层剥离到容器实现层,容器提供商负责事务,ORM,RPC调用,线程池管理,数据库连接池管理,服务实例池管理,消息队列各种『脏活累活』,程序员只管负责实现业务逻辑。蓝图很美好,队友很给力,Tomcat( 1999 ),Weblogic ( 1999 ),Websphere ( 1999 ),全世界的程序员都很兴奋,一时间,所有的企业级应用都在往J2EE平台上转,考虑到那个时候几乎没有多少面向终端用户的软件业务,可以说J2EE是整个软件开发世界当之无愧的王者。在一片叫好声中,也有一些负面的反馈,EJB体系过于复杂,学习曲线陡峭,我记得当时自己花了很长时间去理解EJB的Home接口,Local接口,Remote接口,什么时候应该用stateful Bean,什么时候应该用stateless Bean。至于前端基本奠定了Java bean + html的server渲染模式,跟CGI倒也差别不大。早期的软件开发世界,以一人之力撼动真个业界的现象一再出现,在J2EE开发领域,一个澳大利亚的程序员,Rod Johnson,今天说起来是个传奇了——Spring的创造者。他在2002出版的一本书在整个Java社区引起了巨大的共鸣,大家受够了EJB的复杂性,却苦于找不到更好的解决方案。

Expert One-on-One J2EE Design and Development

然而,这本书其实是『名不副实』的,它基本抛弃了所以的曾经J2EE鼓励的开发实践,他从一个普通程序员的角度,循序渐进的指出应该如何友好的去构建一个系统,正确的面向对象设计实践。直到今天,这本书对于软件开发来说仍然具有相当的指导意义,我自己是常看常新。2004的他的另一本书再次受到了社区的追捧,JavaEye组织的翻译也很好。但就我认识而言,这本书的深度远不及一本书,它没有涉及过多的思考过程,而是机械的告诉怎么做是对的。

Expert One-on-One J2EE Development without EJB

今天看来,结果就是,Spring社区已经一统Java server端的开发,想想还是很震撼,仅凭一己之力。

互联网繁荣期 2005~至今

2005年影响web开发世界最大的就是Ruby on Rails开发框架横空出世,又是一己之力,一个丹麦的程序员,一个24小时汽车拉力赛的世界冠军,当然也是我的偶像。

David Heinemeier Hansson 

他给我最大的启示就是一个程序员要有自己的思想,通过学习,实践的过程来求证,人云亦云最要不得。Rails给Ruby带来了2006的Tiobe的年度编程语言荣誉。一门新兴的语言依托一个强大的框架,直接在互联网开发领域占有一席之地。

我们就简单列一下流行的互联网服务搭建在Rails上,不出名的就不赘言了。

Twitter, GitHub, Shopify, Airbnb, Twitch, SoundCloud, Hulu, Zendesk, Square, Highrise, Basecamp

2005年Python领域的Django web框架也发布了,如今基于Python stack的互联网服务也很多很多了,Dropbox,Instagram,Pinterest,国内的豆瓣。

这个时候一个特点就是『快』,互联网红利完全体现在快这个字上,新兴的业务永远Beta快速发布,甚至Alpha发布,传统的企业级开发模式显然是跟不上这个节奏的。

这个阶段的开发框架大都遵循着『约定大于配置』的简单观念,放弃大部分不必要的灵活性,一个重要的结构烙印深深地刻在这些框架身上,遵循简单的Model-View-Controller架构,搭配框架内置的tool chain,一个命令就可以产生良好的应用主体代码框架,然后再遵循着框架约定,填入业务代码。

MVC

2006年有两件不能不提的以后会影响整个web开发的事是,

  1. 微软在2006年提出了异步请求的概念,并把它的实现加入到了IE 7之中,这就是我们今天熟知的XMLHttpRequest。之所以叫XML是因为它首先作为一个ActiveX控件被Outlook团队开发出来用来从server获取xml格式数据,然后IE 团队就直接拿过来使用了。
  2. 2006年jQuery发布,jQuery影响有多大,想想很多很多的人提请标准委员会浏览器直接内置jQuery实现便得窥一二。好吧,也是一个人,John Resig。可能更年轻的前端开发者已经不太知道大神的地位了,我记得现在流行的前端框架的开发者当时是非常兴奋的在twitter上发布说『John关注我们了,对,就是那个John』。

在2009年之前,这一段时期的web开发,已经有相当多的开发量从后端转移到了浏览器端的javascript的开发,javascript已经不再被当做玩具语言了,早期的时候,甚至浏览器可以配置是否禁止javascript,你可以想象是多么的可有可无。这个时期的代码体现在『绚』和『乱』上,各种绚丽的效果,大家都很兴奋头一次看到这么绚的UI交互效果,大量的jQuery plugin被开发出来。乱,就跟当初server端的代码一样,各种业务逻辑与dom操作混在一起。一些大的互联网公司封装了一些今天看来很重的js的前端框架,其核心思想就是要用开发传统桌面程序的方式来开发web程序,YUI和ExtJS ( 2007 )是其中的代表。

2009的时候,一个新的运行时平台,一个今天异常繁荣的社区出现了,nodejs平台,node社区,还是一个人,Ryan Dahl,他基于Google开源的javascript引擎V8实现了大部分的javascript类库,可不严格的类比于jdk之于JVM。在javascript发展历史上,这是极其决定性的一步。有了这一步,大家才开始考虑要有依赖管理,modular开发,面向对象,异步,同步,这也反过来极大的推动了javascript语言规范的发展,今天再看ES2015语言规范,跟当初的javascript已经完全不是一个样子了。

nodejs平台推出以后,有些技术的先行者就开始考虑能否用开发后端的工程化方式来开发前端,开发完成后,依赖一些构建工具来打包压缩所有的js引入页面。server端只作为api提供json格式的数据,整个应用架构看起来像下面这样子,

API

2009发布的backbone在当时绝对是让人瞠目结舌的,在此之后,更进一步,为了解决双向绑定,局部动态更新的dom操作效率的问题,提出了Virtual Dom和MVVM的方案,然后架构演变成下面这个样子,

MVVM

这样架构的一个潜在问题在于,前端代码的膨胀越来越厉害,即使经过了压缩,于是又有一些聪明人想结合传统的server端渲染技术,在server端引入js运行时来生成静态html代替原来完全在浏览器端用javascript来构建整个前端,这就是所谓的server端渲染技术。

ServerRendering

前端开发架构演变之快,实在是一日千里,单单拎出来前端部分,你看最近几年冒出来多少来势汹汹的前端开发框架backbone,ember,angular,react,vue,再包括各种先进的依赖管理,构建工具体系bower,grunt,gulp,webpack,然而其思想是统一的,工程化,模块化,组件化前端开发。

这样的开发方式,好与不好,见仁见智,对于大团队合作开发必然是有优势的,在我看来,有一点始终是需要提醒自己的,

不是所有的公司都是google和facebook

在软件开发领域,有一句著名的准则,

organizations which design systems … are constrained to produce designs which are copies of the communication structures.

直白来说,软件系统的划分基本上复制了开发这个系统的组织划分。

所以对于小的开发组织,创业公司,才用这种方式要慎之又慎,基本上要求每个成员都是多面手和熟手,不然每个项目面临下面的沟通方式,必然难以为继。

Conway

今天前端开发再不是传统意义上我们所称的『前端』——切一些图,写一些css和简单的javascript了。如今看来,其出现和繁荣可归于两个主要的原因,

  1. 移动互联网的迅速崛起,在移动端,用户需要更快速的响应,更丰富的交互体验,传统的单一请求响应方式,不足以满足这种需求。前端的开发复杂度急剧增大,必须有一种有效的工程化开发方式来指导和构建。
  2. node社区的急速繁荣,这得益于以下原因,
    1. javascript天生就是为web开发准备的,各大浏览器断无可能再发展另一种语言。
    2. Google V8 引擎提供了一个高效的运行时环境,使得基于javascript的server端开发成为可行的途径,V8之于javascript如JVM之于java。
    3. javascript基于回调的机制使其在IO敏感的应用领域表现出更好的并发性,而IO敏感正是绝大多数应用的瓶颈所在,只有极少数的应用需要大量的运算,对CPU有很高的要求。

天下大势,合久必分,分久必合,IT技术领域,不外如是。我们常常会看到当年一些被认为落伍的观念『借尸还魂』,以一种新的姿态,新的概念出现,加之其背后往往有强大的厂商推动,一些开发人员就会鼓掌叫好,跟随学习。这些都值得我们重新审视,从桌面应用到web应用,从J2EE到Rails,从web到native app,从native app到hyprid app,技术历程,一路走来,如果我们能够多一些梳理和反思,少一些狂热与追捧,也许我们便更可能看清自己的技术之路。