开箱:微软 Sculpt 人体工程学桌面套装

抓住实习期的尾巴,利用微软内部的员工优惠,购入了一套 Sculpt 人体工程桌面套装。这一套东西的品牌建议零售价是 ¥ 1999,使用员工优惠为 ¥ 829,我承认这还是很贵(逃

使用体验放在前面:

小馒头鼠标真是舒服,马上我就爱不释手了,爽不可言。再回去用普通的鼠标,感觉简直是在谋杀自己的手腕。

键盘有些学习成本,比如 B 键我总是用右手食指按的,但用这个键盘就必须改到左手;Home/ End/ PgUp/ PgDn 的布置也和我日常用的 ThinkPad 六排很不同。一开始按键的错误率比较高,我大概花了两天才适应好,恢复了之前的代码编辑效率。

健康很重要啊,保护好自己的腕关节、肘关节,也想图个舒服,我感觉很值。

以下多图,无字。

快递箱。微软官方商城,从上海发货。
快递箱。微软官方商城,从上海发货。
气囊和泡泡纸。
气囊和泡泡纸。
内包装盒。
内包装盒。
好吧我承认这张图是后补的,塑料纸已经丢掉了……
好吧我承认这张图是后补的,塑料纸已经丢掉了……
工作站一瞥。
工作站一瞥。

最后,根据美国联邦贸易委员会的规定,再次声明利益相关:I work for Microsoft, BTW. (逃

涨姿势:jQuery 曾经的 XSS 漏洞

最近我管理的一台服务器被怀疑被利用发起 DoS 攻击(详情见长文学习构建之法(2):课程总结),被有关管理单位查了水表。他们附送了一份检测报告,指出我们存在若干安全漏洞。其中一条引起了我的兴趣。

URL:http://SSAST.ORG/static/javascripts/libs/jquery.js。
详情:发现存在漏洞的’jquery’库,版本号:’1.6.2’。
风险:中危。

此外还报告了 jquery-ui-dialog 版本 1.8.11。

科协主页确实很老了,还在用上古时代的 jQuery 的上古版本。令我很感兴趣的是,前端的漏洞,除了 XSS 或者 CSRF 还能是啥?jQuery 这种万众瞩目的库,难道不是经过无数人 review?还能出这种问题?

我选择去找我的好朋友,Google.com。

google-jquery-1-6-2

哈哈,I’m feeling lucky. 点开,果然是 XSS。CVE-2011-4969#9521 jQuery Bug Tracker

我感觉这不能算是 jQuery 的漏洞,而在于程序员对 jQuery 的用法。简言之,很多知名网站和前端库里有 $(location.hash) 这样的写法(然而我无法理解为什么要这样做)。攻击方法是诱导用户点击精心构造的合法 URL,比如 http://example.com/#<script>alert('hello');</script>

jQuery 1.6.3 的 Release Note 指出该漏洞已被修复,方法简单粗暴,$() 用一个正则表达式来过滤,拒绝任何跟在 # 字符后面的 HTML 标签。

学习构建之法(2):课程总结

 

 

年底、期末,在繁忙之中,我和我的团队完成了“微学堂”这一产品。伴随着它的交付,《软件工程》结课了。我现在是本科三年级,两年半以来修了五十多门课,应该说这一门是挑战度最高的。最后几周,不时有通知“下周的课不上了,各个小组自己做团队项目”:相对于课堂听讲,课外需要付出的精力之多,由此可见。

即使以代码行数衡量并不是,我感觉这个团队项目是我经历过的规模最大的项目,大到另我有无法驾驭的恐慌。不过这样说也不公平,这种“out of hand”的感受不仅来自这个项目本身,也有来自其他课程、辅修专业、社会工作等等带来的压力。然而话说回来,我直观上的感受就是,作为组长我不清楚每一个组员都具体做了什么、用什么方法做的。不可能有一人 review 每一段代码;不可能有一人了解项目中的全部实现细节;更不可能凭一人之力完成。

与此相比,此前的和本学期其他的“大作业”都只是小打小闹。不点名提及这学期的某些课程的某些 Project:一人花几个小时就做完,好意思说它是 Project?顶多算个 Assignment 好吧。如果把这些都算上,那么我这学期可能做了 20 个大作业,恩,说出来挺唬人(划掉)。

 

Donald Knuth 有一句名言:过早优化是万恶之源。项目中还存在一些过度设计的问题。其实这些词我都不是很懂,通过这个团队项目,只是感觉对此有了一些浅薄的理解。

中途出现的需求变更对我们造成了不利影响——来自教师的一条非功能性需求:我们被要求使用另一小组提供的 API 服务,而非自己编写相关模块。为了保证进度,我没有选择重构,而是把这个 API 服务做了一层包装,以保持接口不变而替换原有模块。这带来了架构上的冗余和“不优雅”。

这里所说的模块是一组爬虫。爬虫总有个运行频率的限度,而用户总是希望得到最新的信息,而且希望在最短时间内得到最新的信息。针对这一矛盾,我们设计了后台定时触发的“自动刷新”和用户手动触发的“强制刷新”这两种不同的刷新,同一个接口设计同步和异步两种调用方法。结果,因为他们 API 服务没能支持,全都白费功夫啦。

值得一提的是,做 API 服务的那组同学似乎并没有考虑运行频率的限度的问题。他们对爬虫的目标,也就是我校网络学堂和综合教务系统,发起高频次、高并发的请求,形成 DoS 攻击,造成网络学堂压力过大、反复宕机。运行着他们 API 服务的那台服务器,其 IP 被学校封禁。不巧的是,该台服务器是由我管理的。简言之,我因为他们被查水表了……这导致我不得不花大量的精力来与上级管理人员沟通,撰写需要同时抄送给三四个单位的整改报告——这影响了我们的进度。

我们在最初设计时,在安全性上花了很多精力。我们使用 OAuth,我们使用 https,避免用户信息的泄露。实现时,微信公众平台 OAuth 那一套牵扯了我们更多的精力:除代码编写外,开发环境搭建、调试、测试等方方面面都有复杂性的增加。结果呢,直到最后,我们的上游,也就是上文提及的 API 服务,也没有实现任何对调用者身份的验证。数据全部明文传输。他们的 mongodb 数据库甚至不设密码,直接暴露在外(这也是上文提及我被查水表的原因之一,原话是“存在高危安全隐患”)。

只要有一环出了问题,整个安全链条就会断掉。API 组糟糕的安全性意味着我们的用户的一切信息都会被公开在国际互联网上,也就意味着我们在安全性上的努力全部白费——除了在文档中写上一笔,期盼因此获得作业成绩的加分。好气啊!

 

花了这么多篇幅吐槽 API 组。我们自己得到的教训是:应该循序渐进。特别是在敏捷的语境下,不能一开始就想着搞出完美的设计“一步到位”,不能指望一开始就把所有细节都考虑周全。一开始最好是设计得简单些,keep it simple and stupid,在反复迭代中进行改进,边写边重构。

通过这个项目,团队管理上的经验也积累了不少。把一个团队带好,这是非常困难的,我一直觉得我在这方面没有缺少天赋,适合埋头干活,而不是做一个管理者。这回做组长,让我在这方面增加了一些经验,也增加了一些自信(笑)。具体的经验可以列一些,比如重要的事情不能只口头说——在微信群里说也算“口头”——要形成文档,形成书面的东西。

 

最后说回这个课程本身吧。除了做项目的锻炼之外,通过课堂讲授,我的理论水平也得到了提高——SC、CC、MCC、CDC 等度量测试覆盖的一堆词儿;系统的 actor、stakeholder,UML 状态图、顺序图、活动图,用户故事、用例建模,需求工程的那一套理论;接触了几种设计模式;关于团队、流程一些成熟经验……作为一门工科专业课,竟也有了一分管理学的味道。

建议就没什么了,各方面都挺好的。只有一条,那就是一组来搞 API、其他组使用的这种玩法,估计明年不会再搞了吧(逃)

 

这是《软件工程》课程系列博客的最后一篇。
相关文章:
学习构建之法(1):写个微信抢票
django 性能调优手记
“结对编程”初体验

Electronic Trading Challenge 酱油记

上个周末刚参加了 Jane Street 举办的 Electronic Trading Challenge Tsinghua Event。三只大三狗,大作业的汪洋大海之中。成绩是 19 支队伍的第 11。本文也很水,预警一下( ̄(工) ̄)

## 体验

简言之就是新奇、有趣。我都想开个户玩真的了。

还有便是认识到了差距,感觉自己好菜啊( ̄(工) ̄)

## 比赛概况

编写程序在虚拟市场里交易。市场上有 4 支个股、2 个基金,还有国库券。除了参赛队之外,市场里还有做市商。交易所提供每种证券的订单簿和订单成交信息。每个交易日持续 5 分钟,每天都是两手空空从头开始,每天结束后系统按照某种公允价值计算盈亏积分。比赛从上午九点持续到晚上九点,划分为一个一个 5 分钟,选手可以随时修改程序,加入或退出市场。

## 我们队的套利策略

我们首先花了点时间通读文档。叶曦在根据交易所的协议编写 API,我和江昊琛先开始研究怎么赚钱。因为暂无 API 可用,我们就用 nc 命令强行手动下单,结果一单亏了 2 块钱,这 2 块钱使我们队一连几个小时都以负的积分排名垫底……好气哦。

基金可以和对应篮子直接互相兑换,无论价格如何,只收固定的手续费。我们首先看到了这里面的套利机会。例如,X 能兑换 2 A + 3 B + 2 C。某一时刻 X 的买入价是 9,A 的卖出价是 2、B 的卖出价是 1、C 的卖出价是 3。那么我就可以花 9 元买入一个 X,兑换成 2 个 A、3 个 B、2 个 C,然后分别卖出去,得到 2 x 2 + 3 x 1 + 2 x 3 = 11 元。从左手搬到右手,9 元就变成了 11 元,只要兑换的手续费低于 2 元,我就赚了。

实际的价格并不是常数,而是数量的一个分段线性的函数。这个函数可以根据订单簿计算出来。对于买方报价,各段斜率是递减的;对于卖方报价,各段斜率是递增的。

Buys         Sells
Price Volume Price Volume
8     3      9     4 
7     2      11    3
6     2      12    4
4     4

随便编了个数字例子。对于给定的证券,买方报价总是低于卖方报价的,否则就直接成交了。X 的价格满足这个关系,ABC 的价格(A、B、C 的价格的加权和)也满足这个关系,但是 X 的价格和 ABC 的价格之间就不一定了。它们都会波动,很可能存在某些时刻,X 的价格和 ABC 的价格不匹配,这叫市场的瞬时无效性。

ABC 组合的曲线是用 A、B、C 的三条曲线按比例叠加得到的。

上图示意的情况下,我们可以买入 A、B、C,卖出 X,赚取差价。阴影部分就是机会啦。在横轴上找到一点,使买入 ABC 的总价与卖出 X 的总价之间的差额最大。如果这个差值大于手续费,我们就可以动手啦。同时还要关注反方向 X Sells 和 ABC Buys 两条曲线,以发现反方向的机会。

为完成这样的交易,需要同时盯着 4 组订单簿,随时更新 10 个总价格函数。每次操作要同时下 5 个订单。价格差转瞬即逝,这种事情显然只有程序能做。

说实在的,策略很简单,模型也很简单,但我们实在是太菜了。大概上午十一点开始写,晚上五点才把它调试好上线。

说实在的,策略很简单,模型也很简单,但我们实在是太菜了。大概上午十一点开始写,晚上五点才把它调试好上线。在此之前我们队只是简单地倒卖国库券,每个交易日的收益徘徊于两位数;后来叶曦做了几个我搞不懂的策略,提高到几百,但是波动似乎很大。等到这个套利策略上线,收益就稳定在 1000 元左右了。

## 差距

凭着这个套利策略,在比赛的最后几个小时,我们的排名上升了 3 位。后来我们了解到,前几名的队伍每个交易日的收益都在 10000 元以上。数量级上的差距,确实难以望其项背。

## 收获

打了一整天酱油,反正三顿饭都很不错。连续十个小时写代码,感觉很爽的。要说收获,也就是见了见世面吧,体验了一下 Hackathon 这种形式,对证券市场有了些感性认识。

(本文首发于知乎

学习构建之法(1):写个微信抢票

历时 5 周,历经 Alpha、Beta 两次迭代,终于把软件工程这门课的两人项目做完交付了(?)

粗略计算,与我同组的刘斌同学在这个项目上花费了百余个小时的时间。我本人则少一些,未作确切统计。从截止日期临近时另一专业课 13 / (~60) 的出勤率、去校外咖啡馆刷夜的人数、汇报展示现场主讲教师建群发红包“熬夜辛苦,中午吃好点”的情景,可见这项作业的工作量之大(?)

这个项目最主要完成的是测试环节,至于需求分析、软件设计、团队流程等则涉及较少。

根据课程要求,撰写一篇博客。择其重点,总结一下主要的收获。

  • 设计实现
    • 体会到 Web 应用开发时前后端分离的好处
    • 再次体验了给助教华榕大帝改 bug
  • 团队合作
  • 软件测试
    • 认识了 django 测试框架、浏览器自动化工具 selenium 和 PhantomJS
    • 知道了编写单元测试的基本方法
    • 了解了编写功能测试的基本方法
    • 了解了评价测试的主要指标——覆盖率
  • 性能调优,相关博文:django 性能调优手记
    • 性能测试
      • 认识了性能测试工具 JMeter
      • 了解了评价性能的主要指标——吞吐量、错误率、响应时间等
    • 部署和运维
      • 认识了众多 Linux 系统参数、uWSGI 参数、nginx 参数
      • 尝试了基于 docker 的自动化部署
    • 性能优化
      • 体会到若干因素对性能的影响

此系列还有另一篇博文:学习构建之法(2):课程总结

我以为自己发现了 clang 的一个 bug……

摘要:完成体系结构课程作业时遇到 gcc 和 clang 的行为不一样。确切地说是 clang -O1 及以上会得到意外的结果。以为自己搞了个大新闻,结果发现是自己写了 ub(未定义行为,undefined behavior)。

代码如下。

#include <stdio.h>
#include <limits.h>
int test(int x, int n)
{
  int y = (1 << (n - 1)) - 1;
  int le = (x <= y);
  printf("%d, %d, %d\n", x, y, le);
  return 0;
}

int main(){
  int n = sizeof(int) * 8;
  test(INT_MIN, n);
  return 0;
}

gcc 版本:gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4
输出的结果是:

$ gcc bar.c -O1 -o g && ./g
-2147483648, 2147483647, 1

clang 版本:Ubuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4)
输出的结果是:

$ clang bar.c -O1 -o c && ./c
-2147483648, 2147483647, 0

 

好奇怪啊,明明一个正数、一个负数,怎么 clang -O1 就负数更大了呢???

是不是 clang 编译优化的 bug?

——我也以为自己搞了个大新闻。仔细研究一番便发现,这里并不能怪编译器:1 << 31 已经发生了溢出;即使得到了负值 INT_MIN,在其上再减去 1,再次发生溢出。对于有符号整数运算溢出,尽管很常见的做法是截断取模,但实际上标准并没有作出规定。因此,从这一行起,编译器做什么都是符合标准的( ̄(工) ̄)

ub 很可能直接被编译器优化掉[1]。此处就是这样。可以从汇编码看出,开启编译优化时,clang 把 le 参数直接优化掉了,大意如此:

printf("%d, %d, %d\n", x, y, le); //clang -O0
printf("%d, %d, %d\n", x, y, 0);  //clang -O1

完。

django 性能调优手记

最近在做一项课程作业,是基于 django 框架的一个抢票系统。项目的特点是逻辑比较简单、并发要求高。要求支持 1000 个用户并发访问,响应时间小于 5 秒,错误率 0。

    1. 首先我们知道,python manage.py runserver 是肯定不行的,测试表明支持并发数小于 10。
    2. 使用 uWSGI + nginx。基本配置下,支持并发数达到 120。
    3. 调大系统 net.core.somaxconn 值。
    4. 调整 uWSGI 配置参数,此时支持并发数达到 380。
        • 以 IPC socket 取代 TCP socket,节约协议栈开支:socket chmod-socket,对应修改 nginx 配置文件中 uwsgi_pass
        • socket 队列大小:listen
        • 进程数:processes,大致和 CPU 核心数匹配
        • 线程数:enable-threads threads
        • 关闭日志:disable-logging
        • Python 解释器优化级别:optimize

      至此,我们的 nginx 配置大致是

      server {
          listen 80;
          server_name wx.nullspace.cn;
          location ~ ^/(wechat|api|admin|accounts){
              include uwsgi_params;
              uwsgi_pass unix:///tmp/wechatticket.sock;
          }
          location / {
              root /home/justin/WeChatTicket-1611/static/;
          }
      }

      uWSGI 配置大致是

      [uwsgi]
      chdir=/home/justin/WeChatTicket-1611/
      module=WeChatTicket.wsgi:application
      master=True
      max-requests=65535
      socket=/tmp/wechatticket.sock
      chmod-socket=666
      optimize=2
      processes=2
      threads=2
      listen=65535
      disable-logging=True
      enable-threads=True
    5. JMeter 显示并发数更高时会出现很多错误,尽管响应时间是比较低的。查看 nginx 的错误日志发现全部是 socket() failed (24: Too many open files)。后来又出现了worker_connections are not enough while connecting to upstream,为此魔改了一批参数:
      1. nginx 配置 worker_rlimit_nofile
      2. nginx 配置 worker_connections
      3. 修改 /etc/security/limits.conf
      4. 编辑 /etc/sysctl.conf 修改 fs.file-max 值
    6.  优化数据库锁操作
      • 检查余量、减库存的操作是需要加锁的,代码片段原先是这样的:
        # 减库存
        with transaction.atomic():
            activity = Activity.objects.select_for_update().get(id=actId)
            remainTickets = activity.remain_tickets
            if remainTickets <= 0:
                return self.reply_text("没票了,你来晚啦 :(")
            activity.remain_tickets = remainTickets - 1
            activity.save()

        reply_text 是阻塞的 I/O 操作,将其挪到 transaction.atomic() 作用域外:

        # 减库存
        with transaction.atomic():
            activity = Activity.objects.select_for_update().get(id=actId)
            remainTickets = activity.remain_tickets
            if remainTickets > 0:
                activity.remain_tickets = remainTickets - 1
                activity.save()
        if remainTickets <= 0:
            return self.reply_text("没票了,你来晚啦 :(")

        由此可以减少数据库锁的持续时长。测试表明:此处仅稍稍调整了语句的顺序,便使平均响应时间下降了 20%。

      • 顺便指出,如果去掉锁,可以再下降 40% ( ̄(工) ̄)
      • 可考虑使用 django.db.models.F 表达式代替数据库存取和锁
    7. 优化数据库索引
      • 给“票”表增加了一个字段的索引,去掉了一个字段的索引
    8. 避免 SELECT *,减少数据库查询字段数
      • django.db.models.QuerySet.only 方法

 

文中的测试数据是在一台单核 CPU、 768MB 内存、运行 Ubuntu 16.04 系统的 VPS 上,利用 JMeter 取得的。

“结对编程”初体验

本学期选修了清华大学软件学院刘强老师主讲的《软件工程》课程。近期完成了该课程的一项作业——体验结对编程(Pair programming)。本文谈谈个人体验。

我们实现了该课程的作业“紫荆之声——基于微信公众平台的票务管理系统”的一个功能单元:一个事件处理器 Handler,当用户在微信公众号点击“抢啥”菜单项时,返回近期可以抢票的活动列表。这是我们在该项目开发过程中编写的第一个 Handler。至于为什么这个看似简单的小任务花了30分钟之久,大概是因为我们事先毫无准备,两台笔记本搬过来,装上录屏软件就开始了,什么都还没配置呢( ̄(工) ̄)

总的来说,我扮演了驾驶员的角色,刘斌同学扮演了导航员的角色。我的手放在键盘上实际编写代码。同伴则负责进行审查、解答我提出的各种问题、跟我讨论实现上的细节,比如我们应该用请求体的什么参数来识别这是“抢啥”事件?这个地方写得对不对?是否有库函数完成这个操作?这个异常是怎么回事?返回给用户的活动列表应该怎么过滤、怎么排序?他利用另一台笔记本电脑,有时去查阅文档、有时搜索爆栈网、有时打个断点跑起来试试看。有时充当小黄鸭的角色。

相比两人分工各写各的,我感受到的好处主要有几点:不再有冲突合并的麻烦;一个人写另一个人就同时检查了,省去了一些调试查错的时间,也省去了代码审查的时间;沟通成本降低。缺点也有,比如总的工时数似乎并不会下降,而且约时间、找地点有额外的成本。

 

浅尝现代前端开发

“Web 前端技术实训课程”感想(一)

在苹果电脑早已全线标配 NVMe 固态硬盘的当下,我看到有些课程还在讲解磁带。教师自然会有一万种理由来辩解这并不是什么“不思进取”,但作为学生还是总觉得不痛快。

在这样的背景下,Web 前端技术实训课程不可不谓激动人心。该课程由清华大学软件学院刘强老师开设,在该校所谓的夏季小学期展开,历时两周半。邀请到来自知名互联网企业的工程师登台讲解,配合独具特色、实践性极强的“大作业”,有望接触到工业界最新潮流,实在是令人期待。

从今开始在这里分享参与该课程的感想,同时也是为了应对其作业——“在个人网站中发布一些个人日志”。

更新 2016-9-26:就这一篇,并没有后续- –

本博客改名称、换域名啦!

nullspace,数学名词,见于线性代数课程中,对应中文“化零空间”、“零空间”、“核空间”等。

null 和 space 两个词同时出现,是不是很有 geek 科技范?

我的博客的新的地址,是 nullspace.cn(白菜价的 .cn 域名←_←),请大家更新收藏夹~

原域名 200404.xyz 将保持使用直到2016年2月。当然新浪云提供的二级域名 200404.sinaapp.com 会一直可用。更新 2016-9-26:已从新浪云迁出,目前放在 Vultr 提供的 VPS 服务器上。

(完)