作者: meathill

  • 大象喷水

    大象喷水
    清迈大象营,河中洗澡,大象喷水
  • 也说两句对“个人与公司”的看法

    也说两句对“个人与公司”的看法

    知乎上这个帖子在程序员届引发了大量讨论,有人挺有人砸。有人说不该加班,有人说不加班能行么?有人说不该攻击公司,有人说不攻击公司将来会有别的小朋友跳坑的!有人说耗子你拿了大把票子说走走了,兄弟们怎么办?我也在自己的地盘上吐两句槽。

    (本文仅代表个人观点,与就职公司无关。)

    首先表态:

    只有弱逼才不敢骂公司,我之所以抨击前东家还一直说现在的公司(点乐)好是因为真的是这样。如果将来有一天这家公司做出让我看不过去的事儿我一定会该骂就骂,该走就走。

    喜欢加班文化的领导都是傻逼,认为加班的团队才是好团队的领导是傻逼中的傻逼,认为“阿里(这里只是代称)这么些年做到这么大所以它的管理方式是相当科学的所以加班也是科学的”,那就是傻逼中的战斗逼了。

    部门领导的第一要务是帮助公司实现业绩;第二要务是提供稳定的团队。团队成员怎么样和领导其实没什么关系,实际上,领导早点滚把地方腾出来几乎是所有下属的心头想。


    做一个真正的理想主义者必须有两个要素:一,保留理想,追求理想,不轻易放弃;二,认清事实,避免为现实所伤。

    在这件事情上,理想是什么?作为一个把程序员当成终身职业的人,我认为,是:出卖自己的劳动力,换来合理的收入。就这么简单。但是现实世界很复杂,使得这么简单的想法都不易实现。比如,会有一些人,因为时机因缘或者彼得原理,成了领导。指望这些领导能够见贤思齐与时俱进几乎是不可能的,他们为了刷存在感就会想出各种点子增加工作难度,比如,把“工作态度”作为考核标准——所有这种主观因素强烈的东西一旦进了KPI,好好写代码的日子就没有了。

    更有甚者,奉行加班文化。加班这种操作,对于劳动密集型的工厂来说,是有价值的,因为在那儿劳动力就真的是“力”,物理上的“力”,自然人的“力”。里面创造性的东西很少,生产线一铺,只要上足人手保证24小时开工,东西就能源源不断的做出来,投入市场。对于老板来说,就是1+1=2,前面的1是正常工时,后面的1是加班。程序开发则完全是另一回事儿。其中需要大量的思考,大量的脑力劳动,最终落实到磁盘上的,反而是很少的一部分。所以加班在这里毫无意义。你知道我这会儿脑子在想什么么?父母老婆孩子还是狗还是大秘的丝袜高跟?魔兽炉石暗黑三还是LOL还是再也回不去的MC?加班除了空耗大家的时间,没有任何意义。

    还有些早早放弃在技术领域深造的领导,对新技术完全不了解,你给他推荐git的时候,他会说,哦,不就跟cvs差不多嘛。这样的领导,很难对知识储备、代码质量、程序架构、开发效率等产生兴趣,在他眼里,三星工程师和五星工程师的区别无非是3+3+3+3和5+5+5+5的区别,再加上前者多半因为人微言轻,看起来更无害更好管理,后者自命清高不好接触,然后一平衡来个15对18,差不多。实际上呢?在开发领域这些点是乘积的关系,简单评估这几点三星工程师只有81,五星工程师能达到625,随随便便将近10倍的差距。

    所以现实就成了,如何应对因为种种原因成为领导的傻逼(为了照顾很多人的语文水平我不得不强调一下,我绝不是说所有领导都是傻逼,而是说确实有些傻逼混成了领导)?答案无非就是,提升自己,超越傻逼;珍爱生命,远离傻逼。还有就是,如果碰到题中那些舍得花时间去关注效率,鼓励大家做分享的领导,好好珍惜吧。


    这可能是我“傻逼”一词用的最多的一篇文章……

  • Mac OS上各种开发环境的配置(未完成)

    Mac OS上各种开发环境的配置(未完成)

    很多开发人员喜欢 Mac OS,因为它基于FreeBSD,有原生的命令行工具,配置各种开发环境都很方便。我个人偏爱Windows,我觉得各种可视化用起来更爽。

    这篇文章介绍如何在Mac OS 10.10 Yosemite上配置各种开发环境,范围以前端所需为主。

    Xcode

    无论你是否准备开发iOS或者Mac OS上的应用,Xcode最新版都是必须的。因为里面包含了系统必须的命令行工具和编译工具,没有它们支持,我们就无法安装后面那些东西。

    好在Xcode是免费的,虽然体积巨大(而且早年不支持断点续传,我第一遍Xcode装了半个月),但只要你有恒心有毅力,智商正常,就都能装上。

    方法:

    1. 打开App Store
    2. 搜索Xcode
    3. 安装
    4. 安装完成后,在命令行里运行xcode-select -p,如果显示/Applications/Xcode.app/Contents/Developer则表明Xcode 命令行工具已经安装成功,否则的话,执行xcode-select --install安装

    homebrew

    工欲善其事,必先利其器。在*nix环境下装东西,一个好的包管理工具是必须的。我使用的是Homebrew,它的安装非常简单:

    ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
    

    因为Mac OS默认包含并启动了Ruby环境,所以此时系统就开始执行homebrew的安装。关于brew的用法,可以看它的官网,或者运行brew help阅读帮助。

    nodejs

    有了homebrew,一切都好办了。

    brew install nodejs
    node -v
    

    不过我更喜欢标准版,在nodejs的官网下载dmg安装包,直接安装即可。

    ruby

    Mac OS默认包含ruby,不过我喜欢最新版,所以还是拜托给homebrew好了。

    brew install ruby
    

    homebrew会把包安装在/usr/local/Cellar目录下,然后通过软链接链接到/usr/local。有些系统默认包含的组件已经注册在/usr/local或者/usr/bin,创建链接可能会失败。不用担心,homebrew提供了详细的帮助信息,仔细阅读,按照提示一步一步做,就OK了。

    Python & httpie

    python我用的很少,耳闻3和2差别巨大,也就不敢追新。所以我用系统自带的Python,只是安装了 httpie 这个工具来调试远程数据。安装一样是通过homebrew:

    brew install httpie
    http httpie.org
    

    apache httpd

    Apache httpd是Mac OS的默认组件,直接运行sudo apachectl start即可开启apache服务器,然后访问 http://localhost/ 就能看到默认页面了。

    系统默认的文档地址为 /Library/WebServer/Documents,把静态的页面放进去,就能正常浏览,用来测试还是蛮方便的。不过我觉得不算太实用。因为一来埋得很深,通过Finder无法直接到达;二来作为公司配的开发机,一般来说我不建议在公共目录里做东西。

    不过配置起来也不复杂。打开 /etc/apache2/extra/httpd-vhost.conf,里面应该有两个没有真正起作用的范例配置。基本上按照那里即可。

    先放出去,回头再补充好了……

  • 漏装php55w-mbstring导致中文邮件乱码

    漏装php55w-mbstring导致中文邮件乱码

    朋友的WordPress发中文邮件总是乱码,喊我帮忙看看。很奇怪,后台、文章里的中文都能正常显示,看起来一切正常;我在我电脑上搭了一套,同样的代码,发邮件也没问题。

    后来打开phpmailer的debug模式,发现什么都对,就是中文内容都是问号。

    继续往上找到发邮件的函数,运气不错插件留了filter,遂修改模板,add_filter强制转换内容。

    转换前先检查,不是UTF-8再转:

        if (!mb_detect_encoding($content)) {
          $content = iconv('ASCII', 'UTF-8', $content);
        }
        return $content;
    

    结果代码传上去报错,说没有mb_detect_encoding,然后想起来yum安装php时确实默认不包含很多扩展,于是手动安装yum install php55w-mbstring。然后重启apache,发邮件测试,正常了。

    我又想是不是缺少多字节文本模块(Multibyte String)导致原先无法发送中文呢?去掉filter,仍然正常,确实如此。

    总结

    yum安装新版php需要增加源,而新版的源默认不包含很多常用的库,使用的时候最好都装上。WordPress的翻译机制面对多字节文本时,编码不对不会报错,也需要小心。

  • 使用Backbone的正确姿势

    使用Backbone的正确姿势

    2012年来到点乐之后,我开始投身Web应用开发。当时我选择Backbone作为主力框架,刚开始做难免会带着各种旧习惯,只有一边使用一边摸索。最近我终于搞明白使用Backbone的正确姿势,记叙于此,希望能让后来者少走些弯路。

    使用框架时,我的原则是:既然使用了这个框架,就应该按照这个框架的思路解决问题,一定要把它的功能都用上,要按照它的方式组织代码,这样才对得起学习使用的成本。所以下面的“正确姿势”,自然也是奔着全面使用Backbone的内建功能、尽量符合Backbone的设计思路,这样的目的总结的。

    本文假定读者熟悉JavaScript,对Backbone有一定程度的了解。

    View

    我比较同意《JavaScript设计模式》中的观点,Backbone的设计很难讲是更接近MVP还是更接近MVC,而是在Web前端这个大环境下,基于其技术特点,吸取各种设计模式的优点做出最合适的的实现。Web应用中,视图的实现自然应该交给HTML和CSS负责,而在数据变化时更新视图,以及响应用户操作的事情,就交给Backbone.View

    Backbone.View提供非常直观的events帮助我们注册事件,免受使用jQuery的.on.on.on之苦。事件被委托给$el,不仅节省资源,并且做列表类应用需要操作多个子节点时,无需再绑定事件。

    不过请注意,这里的事件委托仍然有赖冒泡机制,所以诸如loaderror等不冒泡的事件无法委托给$el处理(submit在早期IE下也不行,但是jQuery会代发冒泡版),只能手工侦听具体节点。另外,Backbone会把处理函数代理给View的实例,所以函数中的this指向是实例,而不再是触发事件的DOM元素。

    列表的前世今生

    接着,来点代码,看看最常用的UI范式——列表。Backbone中,搭配使用Backbone.CollectionBackbone.View可以方便的实现列表类组件,不过因为Backbone本身的限制很少,实现方法很多。早期我习惯于通过reset方法和reset事件来操作列表,后来慢慢体会到,接下来的做法更合适。

    HTML部分:

        // 这里假设我们要做一个todo列表
        <ul>
          <script type="text/x-handlebars-template">
          <li id="{{id}}">
            <input type="checkbox" name="todo" value="{{id}}">
            {{title}} <time datetime="{{create_time}}">{{create_time}}</time>
          </li>
          </script>
        </ul>
    

    JavaScript部分(使用Handlebars作为模板引擎):

        var ListView = Backbone.View.extend({
          fragment: '',
          events: {
    
          },
          initialize: function () {
            this.template = Handlebars.compile(this.$('script').remove().html().replace(/r|n|s{2,}/g, '');
    
            this.collection.on('add', this.collection_addHandler, this);
            this.collection.on('remove', this.collection_removeHandler, this);
            this.collection.on('sync', this.collection_syncHandler, this);
            this.collection.fetch();
          },
          collection_addHandler: function (model) {
            this.fragment += this.template(model.toJSON());
          },
          collection_removeHandler: function (model) {
            this.$('#' + model.id).remove();
          },
          collection_syncHandler: function () {
            if (this.fragment) {
              this.$el.append(this.fragment);
              this.fragment = '';
            }
          }
        });
    

    之所以建议这么做,因为Backbone.Collection有三个特点:

    1. model的事件会由collection向外转播(相当于冒泡)
    2. 取得数据后,collection会逐个创建model,每次都会广播add事件
    3. .fetch().set()时,新数据中不曾出现的对象(以id为标识)会被移除

    列表中还有一些技巧,将在后文呈现。

    钦差大臣options

    早先取数据时为了向服务器传递变量,或者使用特定的jQuery参数,我经常覆写.sync()方法,甚至直接使用$.ajax()。后来发现,各函数的options(除去少数几个return之外)都会传递到下一个函数,直至最后;期间每一步的参数都会合并进来向后传递。

    所以我们只需要在第一次调用函数时,将需要的值放在options里即可 。比如,要保存model里的数据,API服务器和当前服务器不在同域,就可以这样:

    // 关于xhrFields,可以参考jQuery文档:http://api.jquery.com/jQuery.ajax/
    model.save(null, {
      xhrFields: {
        withCredentials: true
      }
    });
    

    其实,options是个很巧妙的设计。Backbone作为框架,必须给其它库和业务逻辑留出足够的空间,使用options,随便其他开发者传什么值,最后都能传回业务逻辑中。另外,当参数个数比较多的时候,使用options也有助于阅读代码。

    options进阶

    基于“从头传到尾”这个性质,我们还可以发明一些特殊用法。比如上一节的例子,我希望给每个元素增加一个删除按钮,点击后移除元素。重点是:以渐隐动画来表现移除动作。在Backbone中,.destroy()方法会通知服务器删除对象(.remove()方法只是从当前集合中移除model,无法满足需要),并且触发destroy事件,我们可以在这里插入动画;但立刻就又会触发remove事件,所以只是在collection_destroyHandler的时候fadeOut是不够的,还要防止collection_removeHandler在动画结束前直接移除dom。

    这个时候,我们就可以利用optionsdestroy()支持参数{wait: true},可以等待服务器返回成功后才移除model。于是我们就能确保对象已经从服务器上清除后,再以视图体现;同时,只要检查options里是否包含这个属性,就可以知道当前触发collection_removeHandler的是.destroy()还是.remove(),再决定是否立刻移除节点就很容易了。(其实随便传个什么标记都可以,这里使用{wait: true}可以获得更好的体验。)

        // 一致的代码我就不写了
        events: {
          'click .delele-button': 'deleteButton_clickHandler'
        },
        initialize: function () {
          // 一致的代码
          // ....
          // 不再重写
          this.collection.on('destroy', this.collection_destroyHandler, this);
        },
        collection_destroyHandler: function (model) {
          this.$('#' + model.id).fadeOut(function () { $(this).remove(); });
        },
        collection_removeHandler: function (model, collection, options) {
          if (!options.wait) { // 没有wait
            this.$('#' + model.id).remove();
          }
        },
        deleteButton_clickHandler: function (event) {
          var id = $(event.target).closest('li').attr('id');
          this.collection.get(id).destroy({wait: true}); // 服务器返回确认才真正移除
        }
    

    利用options能达成的效果还有很多。比如,有些时候我们想往model里放一些特殊用途的数据,只在渲染时候用,不保存到服务器上。通过研究源码我们发现,同样调用.toJSON().save()会传入含有各种参数的options,而手动调用则不会。于是我们又能根据options里的属性返回不同的数据,满足不同的需要。

    这些实现本文不再一一详述,大家请自行琢磨,欢迎留言探讨。

    和服务器步调一致—— sync & fetch

    有些习惯延续自之前的项目,比如请求远程数据。早期我总想用$.ajax从服务器端把数据取来,然后再resetcollection或者model。直到最近开发新项目:tiger-prawn + lemon-grass(也即点乐后台V5),我开始开发RESTful的后端,配合专为RESTful设计的Backbone,贯彻“同步”思路,于是我终于醒悟,发出文章开头那句感慨——我终于明白使用Backbone的正确姿势了。

    “同步”是Backbone非常重要的设计思路,也是用户体验里非常重要的一环。我们经常要面对多端的环境,尤其是开发企业级应用,多人协作办公,必须保证数据在每个终端看起来一致。我一开始很难理解为什么.fetch()回来数据后,除非指定{remove: false},否则新数据中不再存在的对象会被移出collection。这个疑问后来在开发集体协作的todo列表时得到解答。

    还拿前文的todo列表做例子。想象列表面向一个工作组,组内成员均从列表当中接受工作,完成后勾上checkbox表示结案;别的地方还会有需求源源不断的塞入这个列表中。这个时候,数据同步就显得尤为重要。某甲勾掉一条任务,需要从其他人的列表中勾掉同一条任务。由于我们数据交互的主要手段仍然是Ajax,服务器无法向浏览器发出指令,只能由浏览器通过返回值进行操作,所以,将“同步”作为强制性要求,本地collection根据返回值的变化,该修改的修改,该移除的移除,就是最佳选择了。

    trigger: false未必都有用

    最后再说个小问题。有时候我希望改变路径,但不要刷新页面,比如创建文章/article/create/,.save()后得到文章id,跳转到/article/id。因为仍然处于编辑状态,所以不需要刷新页面。一开始我以为router.navigate(‘#/article/id’, {trigger: false});,加个参数{trigger: false}`就可以防止路由生效,后来发现不行。

    出于种种原因,比如服务器没做重定向,我一直没用pushState。此种状态下,Backbone使用setInterval检查地址栏变化,所以只要地址栏有修改,就会触发相应的路由。

    相关项目

    下面是我做过并且在维护的一些使用了Backbone的项目。这些项目未必都做到了以上几点,所以仅供参考。

    团队培训项目团队培训项目二期,其实从这两个项目中就能看出我思路的变化。

    游戏宝典 手机应用,虽然项目停摆了……找时间更新移植到phonegap上。

    总结

    Backbone是一个轻量级,入侵程度很低的框架。可以很方便的结合各种其他库来使用,对使用者的要求也很少,易于学习。不过如果能以正确的姿势操作,又能达到事半功倍的效果。这篇文章写得很费劲,有些东西总是感觉话到嘴边写不出来,反复修改多次对最后两段仍不满意。哎,以后再说吧,先发了。

  • 新站点,填坑

    新站点,填坑

    Nodejs发展起来后,出现类似WordPress这样的开源博客工具是早晚的事儿。作为前端和新技术爱好者,我一直很关注这方面的动态,所以Ghost出现之后我就准备尝试一下。看过前面博客的同学可能也知道,我买过不少机器,所以很快搭了个新站点出来。

    搭博客很容易,就是不知道写啥,然后Ghost版本从0.3拖到如今都0.5.8了……想来想去决定在上面专发系列文章,比如之前挖下坑的《ActionScript3中的插件模式开发》,正好被几个人催更,虽然只是几个人,但也是鼓励嘛,所以新年要在新博客连载完。

    新博客的地址:http://serial.meathill.com/,敬请关注。
    正在更新的连载:ActionScript3中的插件模式开发,也请关注。

    所以以后就是俩博客一起跑,这个博客记录知识点和生活,那个博客系统的写一些比较大的题目。

  • HTML5跨域开发

    HTML5跨域开发

    HTML5中提供了跨域加载数据的方法,让我们得以从JSONP或者Flash中介等各种绕行方案中解脱出来,更加顺畅地与服务器交流。另一方面,因为PHP是最好的语言……所以在它与Node.js之间,我选择前者作为后端语言开发内容服务。

    这篇文章记录使用jQuery+PHP开发跨域应用时的小心得。

    身份验证

    做身份验证,最简单的办法就是使用PHP的SESSION保存用户信息,于是就要用到Cookie。默认情况下,跨域Ajax请求发起时候不包含Cookie,需要我们主动将XHRwithCredentials属性设为true才行。

    jQuery会把XHR封装成jqXHR,并且不暴露真正的XHR(说实话这点有点难以理解,尤其是在做上传进度条的时候)。然后它提供一个给真正XHR赋值的接口xhrField,所以写成代码就是这样事儿的:

    $.ajax(url, {
      xhrField: {
        withCredentials: true
      }
    }
    

    各种HTTP头

    如果不需要验证用户身份,直接在HTTP头中输出Access-Control-Allow-Origin: *即可。

    我的产品需要验证,那么首先,HTTP头中必须有Access-Control-Allow-Credentials: true;此时对域的限制也严格许多,不再允许像前面那样使用*放开给任何来源,必须指明哪个具体域可以接受。

    关于Access-Control-Allow-Origin的值,规范中的说明是“域名列表或null”,然则接下来的“注意”有点诡异:“实际生产中,‘列表或null’要求更严格。你可以认为它实际只允许单一域名或null,而非空格分隔的域名列表。”——既然如此你干脆写个“域名或null”不就完了……

    总之对于我们而言,返回的HTTP头中还要包含Access-Control-Allow-Origin: http://域名,指定允许作为来源的协议、域名、端口,并且只能有一个(组)。因为通常来说我们开发环境和生产环境不一样,所以这里的域名最好不要写在服务器配置里;使用PHP,通过$_SERVER['HTTP_ORIGIN']取出访问来源,与白名单比对,通过后再输出相应的头,更加合适。

    调试

    我选择JSON作为前后端交流的格式。为了方便浏览器解析(也是HTML5的要求),我还返回了Content-type: application/json头。

    使用PHP少不了使用Xdebug。出现错误时,Xdebug会返回完整的栈,有利排查。但是为了方便阅读,Xdebug还会给返回信息套上<table>结构,这时Chrome的Network工具就会把它解析成奇怪的格式,所以Content-type一定要最后和数据一起返回。

    与之相反的是前文说到的Access-Control-Allow-OriginAccess-Control-Allow-Credentials,这二位必须放在最前面。不然如果出现500错,响应头不包含这两个跨域标记,Chrome就会理所当然地不显示返回内容,也就无法看到错误描述,根本无法排查。

    参考资料

    1. Using CORS
    2. Cross-Origin Resource Sharing
    3. HTTP access control (CORS)
    4. jQuery.ajax()
  • 企业级开发的碎碎念

    企业级开发的碎碎念

    企业级开发的难点在于从普适性和特殊业务需求中寻找契合点。一方面我们希望框架精致简单又结实耐用;另一方面我们还希望满足各种业务逻辑,并提供良好的用户体验。前者可以降低开发成本,更快改进业务后台;后者则决定了产品本身有无价值。

    碰到细碎的需求点,把哪些实现放到框架内,把哪些实现放到业务内,决定着一个框架的体积、易用性、扩展性。目前来看,虎虾柠檬草虽然看起来希望很大,但目前尚未及格,仍然需要努力。希望春节可以折腾到及格,节后公测。

    (更多…)

  • 台北最爱——少帅禅院

    台北最爱——少帅禅院

    台北是个很文艺的地方。各种电影院、博物馆、咖啡厅——顺便说一句,台湾电影院每家上映的剧目都不一样,跟大陆完全不同——不过我的最爱还是位于北投的少帅禅园

    少帅禅园是当年蒋介石软禁张学良的地方,现在则被改造成了一处集餐饮温泉于一体的小型旅游景点。称它为最爱,其实跟历史一点关系也没有,而是因为它的院子实在太美了。

    首先,布置好。这个院子布置得太棒了!踏入园子,立刻感觉旺盛的生命力扑面而来。院子内有山有水,树木花草,各种摆设难以想象的丰富,光是盛放花盆的容器就有3、4种。不仅有花盆做成的小人,还有各式地精,憨态可掬的绵羊,晴天娃娃样子的风铃,甚至连被截下的粗枝中都有猫头鹰藏在里面。我非常想知道园主是个什么样的人。凭想象的话,他(她)一定非常热爱生活,热爱园艺。真的真的想拥有一套这样的院子。

    其次,地势好。少帅禅园面朝西北,背山面河,俯瞰地狱谷,风水极佳。整个院落依山而建,房屋错落有致。

    最后,有吃有喝有玩。小六茶屋(张学良乳名)提供各式饮品和下午茶;双喜(也是小名)汤屋可以泡温泉,泡脚泡全身均可;汉卿美馔,提供正餐。约上三五好友,喝喝茶,泡泡脚,聊聊天,可以晃一下,然后晚上吃饭再泡澡,想想都安逸得很。

  • 告别2014

    告别2014

    年会开完了,该拿的钱拿到了(比去年少了好多,真怀念占山为王坐地分赃的日子),大话也说出去了,所以明年就要更努力了。为敲钟,好好敲钟。