Node.js Best practices

完整的ppt来源于Node.js Best practices,作者Felix Geisendörfer,需翻越

Callbacks

下面是关于解析json文件的示例代码:

var fs = require('fs);
function readJSON(path, cb){
  fs.readFile(path, 'utf8', function(err, data){
    cb(JSON.parse(data));
  }
}

显然,上面的代码没有处理异常情况,因此再加个异常处理逻辑上去:

var fs = require('fs);
function readJSON(path, cb){
  fs.readFile(path, 'utf8', function(err, data){
    if(err) return cb(err);
    cb(JSON.parse(data));
  }
}

还没有结束,我们还没考虑到文件内容不是json这种情况,会导致parse出现异常,因此再处理一下:

var fs = require('fs);
function readJSON(path, cb){
  fs.readFile(path, 'utf8', function(err, data){
    if(err) return cb(err);
    try{
      cb(JSON.parse(data));
    }catch(err){
      cb(err);
    }
  }
}

对于cb来说,仍然无法区分正常结果和异常内容,这个问题通常可以通过增加一个err参数来处理,如:

var fs = require('fs);
function readJSON(path, cb){
  fs.readFile(path, 'utf8', function(err, data){
    if(err) return cb(err);
    try{
      var json = JSON.parse(data);
    }catch(err){
      return cb(err);
    }
    cb(null, json);
  }
}

这个示例告诉我们,对于callback,需要时刻准备应付正常结果和异常情况的处理。

再来看看另外一个常见错误:

function readJSONFiles(files, cb){
  var results = {};
  var remaining = files.length;
  
  files.forEach(function(file){
    readJSON(file, function(err, json){
      if(err) return cb(err);
      
      results[file] = json;
      if(!--remaining) cb(null, results);
    }
  });
}

这里隐含了一个常见的场景:批量处理时,任意一个失败,及时退出。有时候可以用标识符,这里采用另外一种手法:重置回调方法

function readJSONFiles(files, cb){
  var results = {};
  var remaining = files.length;
  
  files.forEach(function(file){
    readJSON(file, function(err, json){
      if(err){
        cb(err);
        cb = function(){};
        return;
      }
      
      results[file] = json;
      if(!--remaining) cb(null, results);
    }
  });
}

Nested Callbacks

先看看一个恐怖的例子:

db.query('SELECT A ...', function(){
  db.query('SELECT B ...', function(){
    db.query('SELECT C ...', function(){
      db.query('SELECT D ...', function(){
      });
    });
  });
});

活生生就是一个怪物:),多层嵌套的回调不是很好的风格,我们需要一些流程控制的东西来辅助,例如Control Flow Libs:

var async = require('async');

async.series({
  queryA: function(next){
    db.query('SELECT A ...', next);
  },
  queryB: function(next){
    db.query('SELECT B ...', next);
  },
  queryA: function(next){
    db.query('SELECT C ...', next);
  }
  // ...
}, function(err, results){
  //...
});

像上面的代码,最明显的地方就是异常处理被完全隔离出来。如果要把代码分布到很多小方法里边的话,Node.js的确不是很容易做到

Exceptions

通常throw new Error(msg)可以让你的程序进行异常退出,并在控制台上输入错误信息和堆栈信息。

但有时候我们要考虑的是,一些未知的bug,例如下面一个有bug的示例:

function MyClass(){}

MyClass.prototype.myMethod = function(){
  setTimeout(function(){
    this.myOtherMethod();
  }, 10);
}

MyClass.prototype.myOtherMethod = function(){};

(new MyClass).myMethod();

我们可以采用Global Catch的方式:

process.on('uncaughtException', function(err){
  console.err('uncaught exception: ' + err.stack); 
});

更好的处理方式是:进程挂了,认栽了,到更高层面上去处理

process.on('uncaughtException', function(err){
  // You could use node-airbake for this
  sendErrorToLog(function(){
    // Once the error was logged, kill the process
    console.err(err.stack);
    process.exit(1);
  }
});

Deployment

比较初级的方式是采用node直接运行或在后台运行。老手可能会采用一个脚本来搞:

#! /bin/bash

while :
do
  node server.js
  echo "Server crashed!"
  sleep 1
done

专家级采用的方式可能是(借助成熟的集成工具):

#!upstart

description "myapp"
author "felix"

start on(local-filesystems and net-device-up IFACE=eth0)
stop on shutdown

respawn # restart when job dies
respawn limit 5 60 # give up restart after 5 respawns in 60 seconds

script
  exec sudo -u www-data /path/to/server.js >> /var/log/myapp.log 2>&1
end script

当然,还有些创新风格的(基于托管平台的):

$git push joyent master
$git push nodejitsu master
$git push heroku master

没有托管平台的话,借助成熟的工具应该是最好的选择,性价比高。

2012年总结

转眼2012年就结束了,开启2013年的旅程了。按照惯例还是要给2012年做个总结, 顺便给2013年定些目标和计划。

2012年的工作

2012年工作的情况还是比较顺利的,大概是完全习惯了公司的工作环境和节奏的原因吧, 在项目的开发工作上没什么特别的变化,主要是在日常需求开发外有了比较大的变化。

其中有个变化点,就是参与部门招聘的面试比较多,有段时间几乎天天都要花时间出来面试。 整个过程让自己对应聘员工(主要是应届-2年内)的就业心态有了更多的了解, 虽然我偏重的还是技能方面的考察,但也会尽量的从多方面去考察对方的能力, 希望给一些愿意干的人的机会。另外一方面,看到太多应聘员工对技术浮于表面, 在基础上欠缺太多,这些人大多出自于培训机构, 这也是我一直以来对北x青x类培训有些反感的原因吧。

还有就是,为了激发大家对技术的热情,在部门内组织多次的技术分享活动。 搞这些活动,主要用来锻炼自己的执行力和演讲能力,扩大自己在部门内的影响力。 我觉得,在2012年算是做得不错的一件事吧,效果也不错。

关于工作上的事情,跟外围系统、厂商、客户打交道的次数多了起来,的确挺烦人的, 处理不好还容易惹麻烦,我自己在这方面就吃了几次亏,虽然最终都是化险为夷, 以后在这方面还是得多跟有经验的同事讨教。

2012年的生活

2012年,总算把户口的事情搞定,把拖了许久的结婚证给搞定了, 所以今年初要把婚假也给处理了,先放松一下吧,公司的整个氛围的确是挺压抑的。

在公司的杂事多了起来,有时候弄得本职工作必须得快下班才处理,在时间、任务管理上不注意, 没有太多时间陪伴家人,真的感到愧疚。更大的问题是,公司的问题有时候会影响自己的情绪, 常常回想起来感觉很纠结。

自从有了ipad之后,我也不需要经常跑图书馆了,虽然纸质书很不错 (12年我在oschina的读书活动中捞了好几本书),但大多数书籍毕竟不是经久耐读的, 电子版足够了。12年没统计看过多少本书,感觉20+应该有的。

12年下半年之后重新在github上开了博客,因为公司网络限制的原因,几乎是2年没写过了。 到现在,差不多是每周1到2篇的样子,按照以前的写作经验和习惯,这个数量应该可以翻一翻。 不过考虑到现在的工作状况,每周2篇左右是一个比较实际的数字。

2012年的技术

重拾了ruby,在公司内部为团队开发了一个每日code diff的应用,效果不错。
主要学习了golang、html5和node。
对服务化api与多客户端的架构非常感兴趣。

TODO 2013

多点时间锻炼身体。
在时间、任务管理上多多下工夫,重实效、轻形式。希望能够更好掌握自己的时间。
13年完成100篇原创博文。
13年完成50本书的阅读目标(不一定就技术的),过几天整个列表,好跟踪。
完成对codediff的改进工作,有机会的话多做些内部工具。
与同事、应聘人员、合作厂商、客户的交流上,多积累经验和技巧。

超级整理术简单笔记

今天去体育西那边,看到戴玉堆了一桌子的书,挖了本超级整理术的书,在坐地铁的时间看完了。 内容非常浅显直白,原则就是让信息自动化,让工作规则化,让大脑有更多时间思考。

下面简单记录了比较可行的操作。

  1. 使用谷歌搜索来处理文件查找,桌面搜索的好处是可以直接查找内容,另外找文件名的话, everything也不错,我通常让它自动启动,另外用快捷方式操作程序的话,ctrl+r或launchy都是非常实用的工具。

  2. 使用rss订阅,通常有邮箱订阅或者鲜果之类的客户端,google reader也有很多人推荐, 不过鲜果没有展示博文列表的功能,很难看到哪些更新过。所以最好还是使用google reader, 客户端的话,我发现FeedDemon非常不错,和reader配合天衣无缝。rss订阅有很多好处,主要是信息自动飞进来, 通过阅读大量的信息,开阔视野,增强判断力,有助于在信息大爆炸时代辨别那少量的有用信息。 有个遗憾就是,reader在公司用不了,只能在家里用。

  3. 邮件只读一遍,尽快回复,对邮件分门别类。我通常是把没处理完的标红,然后消灭之。 书中提到说,养成这个习惯,让别人对你有好的印象,有助于推动别人和协作。

  4. 将大脑清空,把所有要做的事记录到TODO上,这个非常重要。 我通常使用wunderlist来处理,无论工作事情还是生活事情, 无论大事小事,急事闲事,都记录到上面去。即使这件事情很琐碎或者很紧急,记录上去也是好处多多的。 wunderlist的功能非常简单,支持多客户端。友情提醒一下,工具这东西,不要挑复杂的。 当然,有时候我还借助番茄钟来管理。

  5. 利用TODO整理时间,使用节约的时间提升自己。做事的时候按照TODO的列表弄,就不至于手忙脚乱了。 我通常是使用wunderlist的加星功能,只是关注加星的事项。 因为TODO帮助我们把时间分配到合理的事情上去,防止时间碎片化,所以不至于瞎忙了。

  6. 工作台和电脑桌面要整洁。作者提倡的是整一个整理日,定期清理。

  7. 开会议题要具体,责任要落实,使用会议纪要并用TODO跟踪。 作者还提到,开会要注意成本,所以开会前就要把讨论事宜详细列出,记录会议纪要并跟踪完成情况,我们公司的会就是效率不行呀。

hello,coffeescript

周五下午的时候,花了大半个小时在团队里边介绍了CoffeeScript,使用的是Sam Stephenson的ppt。 这里再把相关的内容总结一下。

Why CoffeeScript

CoffeeScript关注的是Good Part Of Javascript,可以和javascript无缝结合。 CoffeeScript借鉴了ruby的语法,参考python的缩进规范,让代码更为简洁,规范性更强,适合以javascript为主的应用,如Node应用。 以我的经历(弄个html5 canvas+node的应用),语法入门很简单(如果熟悉ruby,简直就似曾相识),代码量可以少30%~50%,但对像Extjs这样的巨无霸UI代码,由于嵌套太深,不是很合适。

JavaScript’s Good Parts

**It’s private by default. **
所有编译后的代码都会在一个自执行的闭包中,所以不会渗漏出全局变量。还记得在js文件中定义的变量在外边还能访问到吧? 在coffeescript中,全局变量必须要显示指出。

No more “var” keyword.
在js中变量只有在全局或函数内有效,没有局部变量这种,而且没有声明var的话,就会变成全局的。在coffeescript中遵循了这套规则,给所有变量加上var,并在全局开头或方法开始全部声明。 这样可以减少一些粗心导致的bug。

Strict comparisons.
严格相等有了专门的语法is。对于javascript来说,相等这东西让不少初学者踩地雷,特别是对false这东西的理解。

有趣的语法

对着ppt或者coffeescript的官方指南看看就行了。我挑一些自己特别喜欢的:

  • Significant white space 空白是有意义的,纠结于编码格式规范真是扯淡的事,这下好了不用争了
  • Comprehensions 这是非常有用的语法糖
  • Classes and Inheritance 内置的类和继承,不用纠结与用什么方式实现类比较好
  • Bound functions 解决this应用的内置方案
  • Conditionals 写过ruby的童鞋就知道了,后置的if和unless语句爽呆了,对可能为undefined和null的对象调用方法也优雅许多了
  • String Syntax 多行字符串,可内嵌参数的字符串,拼接字符串的时代过去了

How to Use

  • CoffeeScript.org有一个实时编译成js的工具(页面)
  • Rails3.1已经支持CoffeeScript
  • 使用js版的编译器,并使用type为text/coffeescript的script标签,嵌入CoffeeScript代码或文件
  • 使用Node.js,可以用npm install -g coffee-script,然后使用coffee命令编译成js文件
  • 可以使用监控工具来实时编译js文件,例如使用watchr
gem install watchr
watchr project.watchr

# project.watchr
watch('src\/.*\.coffee') {|match| system "coffee --compile --output js/ src/"}

unzip引起disk full问题(旧)

很早的记录了,以前瞎整过很多东西,还好很多资料有整理,所以看上去还有点小用的就迁移过来吧。:)

因为184解压一个oracle dmp文件的时候出错,导致数据无法同步。今天尝试解决这个问题,在日志中看到是解压的时候, 出现write error (disk full?).continue?(y/m/^C)”。发现db.dmp原大小是2.05g,压缩后400m,但是使用unzip解压到2g的时候,就出现上述错误。 但是通过df也没有看到磁盘空间不足。这就有几样可能,有可能是系统限制了或者解压工具限制了。使用ulimit发现文件大小是没有做特别限制的。 搜索一番,有可能是出现unzip的对大文件解压的限制,参考http://osde.info/HowToUnzipLargeFiles,对unzip进行重新编译,结果就可以了。 后来使用which的时候,发现oracle的bin自带了一个unzip,而这个版本也是不支持大文件解压的,但是有人却把这个路径放到path上去了。 最后嘛,我担心改动path路径对oracle造成影响,就修改了恢复脚本的unzip路径。结果总算搞定了。

简略翻译修改版:

# 从http://www.info-zip.org/下载源代码
cd unzip-5.52
vi unix/Makefile

# 使用:/Linux on查找到下面这段描述
# Linux on 386 platform, using the assembler replacement for crc32.c. (-O4 and
# -fno-strength-reduce have virtually no effect beyond -O3. Add "-m486
# -malign-functions=2 -malign-jumps=2 -malign-loops=2" for Pentium [Pro]
# systems.)
linux: unix_make

# 在这段的下面可以找到并进行替换
CF="-O3 -Wall -I. -DASM_CRC $(LOC)"\
CF="-O3 -Wall -I. -DASM_CRC -DLARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 $(LOC)"\

# 保存后下面就是一些编译替换的后续工作了
make -f unix/Makefile linux
which unzip
cp unzip /usr/bin/unzip

关于gcc -D_FILE_OFFSET_BITS=64的描述(找不到原始出处了)

In a nutshell for using LFS you can choose either of the following: Compile your programs with “gcc -D_FILE_OFFSET_BITS=64”. This forces all file access calls to use the 64 bit variants. Several types change also, e.g. off_t becomes off64_t. It’s therefore important to always use the correct types and to not use e.g. int instead of off_t. For portability with other platforms you should use getconf LFS_CFLAGS which will return -D_FILE_OFFSET_BITS=64 on Linux platforms but might return something else on e.g. Solaris. For linking, you should use the link flags that are reported via getconf LFS_LDFLAGS. On Linux systems, you do not need special link flags. Define _LARGEFILE_SOURCE and _LARGEFILE64_SOURCE. With these defines you can use the LFS functions like open64 directly. Use the O_LARGEFILE flag with open to operate on large files. A complete documentation of the feature test macros like _FILE_OFFSET_BITS and _LARGEFILE_SOURCE is in the glibc manual (run e.g. “info libc ‘Feature Test Macros’”).