2017年末总结

又到了一年一度的年终总结了。

今年我个人来说还是比较满意的:欧皇了一把(赶末班车抽到了H1B并且还没有被REF);同样在专业领域(码工)上有了一些收获,接触了前沿科技;顺便在Python的技能树上又走了一步(这下彻底跟Java告别了)。

专业

去年年底开始学Docker,学Microservice,学各种设计理论。学到最后感觉自己跟神棍似的。(这其实是知识储备和经验方面不足的原因。)后来开始多读代码,自己做PoC之后,做交叉验证时一点点体会书本上描述的东西。

其他收获:

  • 读完了《JavaScript the good part》
  • 读完了《Building Microservice》
  • 读完了《Effective Modern C++》
  • 读完了《编程珠玑》
  • 在读 《Fluent Python》

工作

换了新的环境,新的办公室,新的同事。开始用新的技术,新的工具。

在工作中也结识了很多有趣的人,不同专业背景碰撞下经常会有一些很有趣的想法。(虽然每次开场都是我们有想法和程序员,就缺钱了)。

旅行

去了阿拉斯加,看到了极光。

其他

月半了。。。。。

真码农从不用pascal

译自:http://web.mit.edu/humor/Computers/real.programmers
在编程的黄金年代,码农和非码农是很容易区分的: 懂计算机编程语言的就是码农,反之就是非码农。真·码农总爱说`DO 10I=1,10′ 或者`ABEND’ (你懂的,他们永远只用大写字母)。 其他人都在说“计算机对太难了”或者“我不喜欢和电脑打交道,它们太冷冰冰,没有一点人情味”。

但是,时至今日,我们已经来到了一个计算机大爆炸的时代了 – 你家里随便一台家电里都运行着定制的系统,小孩子玩的ipad里面运行着世界上最先进的几个操作系统之一。任何人都可以花个几千块从商店里买一台属于自己的电脑。人人都可以接触电脑,人人都可以学习编程使得真·码农这一物种濒临灭绝,取而代之的是80后的codecademy毕业生。

所以,找到一个方法来快速准确的判断一个码农是codecademy毕业生还是真·码农迫在眉睫。 如果两者直接区别很明显,那么对于codecademy玩家来说,真·码农就是他们人生的下一个目标;对于HR来说,这些差异也可以告诉他们为什么用codecademy毕业生来替换真·码农是一件石乐智的事。

语言

判断一个码农是真还是假,最简单直观的方式是看他擅长的语言。 真·码农用FORTRAN,80后用PASCAL。曾经有人问PASCAL的设计者Nicklaus Wirth 如何念自己的名字。他说,如果你按照名字(name)念,那应该念“Veert”, 如果按照值(value)念,那应该念(Worth). 从这个回答里我们可以立刻判断Nicklaus Wirth是个codecademy 选手:真·码农只认同返回值这一种参数传递机制。真·码农们从不关注过多的无用的概念,在他们的日常生活中,键盘,啤酒和FORTRAN VI的编译器就完美满足他们的需求。

  • 真·码农用FORTRAN处理列表
  • 真·码农用FORTRAN处理字符串
  • 真·码农用FORTRAN报税/炒股
  • 真·码农用FORTRAN实现AI
  • 。。。

如果一个需求不能用FORTRAN解决,真·码农会用汇编来解决。如果汇编也解决不了 – 那这个需求就不应该存在。

 

结构化编程

计算机科学在过去几年里引进了“结构化编程”。很多学者都认为使用结构化的方式来编程,程序会更容易被理解。但是他们给出来的例子并不具有普适性。这使得很多刚毕业的学生认为自己是世界上最好的码农:他们可以用五种不同的语言来写一个五子棋游戏!可以用20行代码翻转二叉树!但是大多数时候,现实世界的任务是:读一个20万行代码的文件,理解并尝试去优化它。

真·码农不会被各种“结构化”定义所约束,他们一般都:

  • 敢于用GOTO
  • 可以写一个5屏的for 循环,并且能快速的找到每一个大括号对应的另一部分
  • 喜欢在if中计算表达式 —— 使代码具有加密性并自动筛掉读不懂表达式的
  • 真码农喜欢写自修改代码 —— 为了节省哪怕1纳秒的运算时间
  • 从不写注释:代码自身的逻辑很“浅显易懂”
  • 由于FORTRAN没有结构化的IF,REPEAT,UNTIL 和CASE,真·码农会用GOTO来模拟它们

数据结构,接口,抽象类,指针等等结构化数据也变的越来越流行。Wirth(PASCAL设计者) 写了一整本书来教你如何使用这些结构化数据来编程。但是在真·码农的世界里,只有一样数据是结构化的:数组。字符串,链表,集合 —— 他们都是用数组来实现的。

 

操作系统

真·码农用什么操作系统?windows?在真·码农眼中,windows就是玩具——小学生都知道该怎么操作它。

Linux看起来复杂很多。 但是大多数时候人们不会用Linux来做真正的编程,他们大多用Linux来写论文,玩MUD游戏和上telnet bbs。

真·码农向来只用OS\370(注1)  。一个好的码农可以在JCL手册中找到如何解决IJK305I错误,一个伟大的码农可以不用任何参考手册来编写JCL代码。一个真·码农可以用心算来发现在6MB核心中的数据转存错误。

同时,OS\370 是一个真正卓越的操作系统:一个bit的数据错位就可以毁掉你几周的工作数据。所以开发者必(bei)须(po)保持警觉。

 

编程工具

真·码农用什么开发工具?理论上来说,只要你把代码敲进电脑的终端,它就可以运行。在计算机有终端前码农如何编程? 真·码农熟知16进制的内存引导数据。每当他写好代码后,都会加上引导代码强制计算机来运行它。(在那个时候,内存还是有记忆的——断电并不会抹去其中的数据。现在的内存变得非常健忘,只要断电就会忘了所有的数据)。

我认识一个真正的码农Jim,他在德州仪器上班。有一天他接到一个客户的电话,声称他的系统在保存一个关键数据时崩溃了。Jim 让他把电话线接口接到计算机上(那时候的计算机有电话接口),并用0/1两个按钮远程重写了部分代码。一个真·码农的工具箱里总会有一个键盘和一个打卡器,有了这两样工具,他就可以写出各种代码。(当然,真正的码农从来不用鼠标)

大多数系统提供了几种文本编辑器供码农使用, 真·码农会小心的挑选一个能反映他风格的编辑器。很多人认为Emacs/Vi是世界上最好的编辑器。这些编辑器的问题是,真·码农认为它们宣扬的“所见即所得”的概念很糟糕 —— 他们更需要的是一个“你要我有”的编辑器。TECO就是这样的编辑器。TECO的命令类似另一种序列化编程语言。(一个著名的娱乐游戏就是,你在TECO命令行里输入你的名字,猜猜它会解析成什么命令组合)。

基于以上的编辑器弊端,很多真· 码农认为直接输入二进制代码效率更高。 下面是真·码农绝对不会用的工具:

  • FORTRAN预处理工具:如MORTRAN, RATFOR等。
  • 调试器——真·码农只会阅读core dump
  • 检测数组边界的编译器
  • 版本控制

 

娱乐活动

真·码农的娱乐生活和他的工作方式很别致。下面是一些能帮助你快速判断一个人是真·码农的秘诀:

  • 爬梯中,躲在墙角讨论操作系统安全性的那撮人
  • 足球场上,拿真人和ps2游戏里的模拟选手比较的那撮人
  • 沙滩上,在沙子上画流程图的那撮人
  • 在超市里,执意自己用激光扫码器结帐的人:他们完全无法相信收银员(尽管他们都用键盘)。

 

日常生活

真·码农的生活环境与其他人不同,为了让他们能发挥最大的工作效率,真·码农应该坐在终端前面,四周环绕着:

  • 一个列着所有真·码农工作过的项目列表。
  • 几杯没喝完的冷咖啡,有没抽完的烟屁股更佳
  • 《操作系统》《编译原理》《JS the good part》之类图书基本。有折印更佳。
  • 一份1969年的日历(方便计算时间戳)
  • 在办公室某个抽屉里藏了一盒奥利奥饼干
  • 饼干下面是一个流程图模板

真·码农可以连续待机长达50小时,他们只需要在编译的时候打个盹就足购休息。如果日程安排的没有那么紧张,真·码农可能会先用90% 的时间做一些与项目无关的玩具,然后在最后一周干满50小时结束项目。当然,这会给项目经理造成极大的心理阴影。所以要了解, 一个真·码农的日常是:

  • 从不朝九晚五
  • 从不打领结
  • 从不穿高跟鞋(不论男女)
  • 通常在午饭时间到公司
  • 真·码农可能知道也可能不知道他老婆的名字,但他一定能背下来整个ASCII表
  • 真·码农从不下厨。

并且,真码农从不用PASCAL

 

两年,四年,又一年

一掐指,来美国已经四年了。再一周,就是工作两年纪念日了。

最近公司来了几批新人。刚入职的时候,我看他们一个个都加班到8点多,还会去说一句,放着明天干吧。然后他们就会说,新人还是努努力,毕竟刚入职要给老板一个好印象。我突然想起也就是两年前,也有人这么劝过我,而我也是这么回应的他。

而后我就慢慢的变成了小油条。

 

有一天组长在给别人电话面试,问了对面,看你简历里说你精通Java,然后如何如何。我就问组里一位元老,你觉得你编程水平如何。他想了下说,应该是能按时完成任务吧。我想了下,我应该是偶尔会超时,但还是能完成任务的那种水平吧。

 

距离离开之前公司有小半年了,有时候也会收到之前同事发来的消息:某某组被裁啦,某某离开公司啦,我们某某项目被砍啦之类。偶尔我也能插两句话,吐槽一下前公司。一个搬办公室的周末,在往箱子里装东西的时候,我觉得自己还在老东家搬砖。然后甩甩头,再把一本书放进箱子里。再贴上自己的名字。

 

记得Z回国之前,我们小聚了一下。聊着聊着就又回到大学时光。我跟她说,以前写博客总想加个个把单词,给自己充充门面,现在写东西就克制自己不要加单词,怕亲戚朋友看不懂。

 

胡言乱语,以后要多练练文笔。

Ch2 中间件 or Controller

(题前话:我本来一直以为这东西都叫controller的,结果被徐同学打脸表示在Node 的Express框架里统称中间件,所以这里就叫Controller好了)

什么是Controller

在上一话中,我们使用了简单的“hello world”作为响应,主要是测试我们的基础框架是否可用。但是,作为一个功能齐全的框架,我们怎么可能对所有的请求都返回”Hello world”呢。

所以,这里我们需要Controller出场了。

从基础概念上来说,Controller就是对 request 与response进行处理的模块。

在MVC中,M为data modeling,即与数据层通讯/抽象/封装,以及算法实现等的模块。V是View,就是设计师设计出的页面模板。C就是Controller,负责转发,处理请求。

Glance at Controller

首先,为了方便Lambda的拓展,以及未来框架的拓展,我们定义一个接口。

[code lang=”java”]
@FunctionalInterface
public interface IController {
// main logic

void execute(HttpServletRequest request,
HttpServletResponse response)
throws Exception;
}

[/code]

这个接口被FunctionalInterface标记修饰,而且只有一个方法,这样我们就可以用lambda表达式快速实现一个Controller对象。

With Route

有了Controller,下一个需要的就是Route。Route的作用,就是将用户请求的URL,如实的映射到预设好的Controller上。

所以,我们需要定义一个 Route接口,用来将URL与Controller结合在一起。

[code lang=”java”]

public interface Router {

void addController(String url,
IController controller,
String method)
throws Exception ;

IController route(HttpServletRequest request)
throws Exception;

List<String> routeRules();

}

[/code]

一般来说,URL 的格式类似于: [protocal name]://[hostname]/[path]/[to]/[resource]/{parameter}?{other_parameter}

在我的实现里,我利用了正则来做匹配。并保留{}圈住的内容作为传入参数。

[code lang=”java”]
private static Pattern variablesPattern =
Pattern.compile("\\$\\{(\\w+)\\}");
[/code]

这个语法就是用Java的正则库将大括号包住的变量“抽”出来处理。

匹配的代码如下:

[code lang=”java”]
@Override
public boolean match(HttpServletRequest request) {
Matcher matcher = pattern.matcher(request.getRequestURI());
if (matcher.matches()){
if (pathVariables != null && pathVariables.size() > 0){
for (int i = 0; i < matcher.groupCount(); ++i){
request.setAttribute(pathVariables.get(i), matcher.group(i + 1));
}
}
return true;
}
return false;
}
[/code]

(全部代码请翻阅 https://github.com/andysim3d/JxpressTutorial/tree/ch2 ,限于篇幅这里只有部分代码)

Bind

创建一个类,存放router 与 controller 实例各一。

[code lang=”java”]
public class MatchAndController {

private final UrlMatcher matcher;
private final IController controller;

public MatchAndController(UrlMatcher matcher, IController controller)
{ this.controller = controller
; this.matcher = matcher;
}

public UrlMatcher getMatcher(){
return matcher;
}

public IController getController(){
return controller;
}

}
[/code]

同时修改的还有JettyServer里面的list,之前我们存放的是静态url,现在,需要存放的是MatchAndController。

[code lang=”java”]
public class JettyServer implements WebServer {
//…
//change here
protected UrlRouter urlRouter = new UrlRout

//change here
public WebServer get(String url, IController ctl) {
try {
urlRouter.addController(url,ctl,"get");
return this;
}
catch (Exception exp){
// do nothing
}finally {
return this;
}
}

public WebServer start() {
//…
//change here
Handler hdlr = new WebServerHandler(urlRouter);
//…
}

private static class WebServerHandler extends AbstractHandler {
// change here
private UrlRouter urlRouter;
public WebServerHandler(UrlRouter urlRouter){
this.urlRouter = urlRouter;
}
public void handle(String s, Request request,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse)
throws IOException, ServletException {
try{
process(httpServletRequest, httpServletResponse);
}
catch (Exception e){
e.printStackTrace();
}
}

private void process(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
try {
urlRouter.route(request).
execute(request, response);;
}
catch(Exception e){
}
}
}
}
[/code]

Test

将入口方法写成如下形式:

[code lang=”java”]
public class Main {

public static void main(String[] args) {

WebServer server = new JettyServer();
server.get("/", (req, res)->{
OutputStreamWriter optwriter = new OutputStreamWriter(res.getOutputStream(), "utf-8");
optwriter.write("Hello You!");
res.setStatus(200);
optwriter.flush();
}).get("/test", (req, res)->{
OutputStreamWriter optwriter = new OutputStreamWriter(res.getOutputStream(), "utf-8");
optwriter.write("Hello There!");
res.setStatus(200);
optwriter.flush();
}).
listen(8080);
server.start();
}
}
[/code]

然后用浏览器分别访问 localhost:8080/ 与 localhost:8080/test

大功告成!

What did we do

简单来说,我们定义了一个叫做IController的接口,这个接口负责接收请求,并返回响应。

为了保证对应的请求能被正确的IController实例处理,我们又定义了一个Route的接口,它负责匹配定义好的路由与请求。

接下来,我们将IController与Route对象一对一绑定在一起,放在容器中(因为我们会有多个Route+IController)。在有请求进来的时候,我们遍历所有Route+IController对象,并试图匹配。(所以如果有多个可匹配项,那优先匹配第一个定义的)。若找到匹配项,则利用对应的 IController来处理请求。

And … then?

下一次教程,我们将要开始搞定中间件与异常处理这两个模块。

[教程] ch1 Web界的Hello World

Ch0中,我们大概讲述了需要做一个怎样的东西。那么,从这篇开始,我们就着手搭建这样一个框架。

创建工程与解决依赖

所谓万事开头难。我们先定一个小目标:跑起来一个空的框架,这样再往里面添加东西就方便很多,也更直观一些。

首先,创建一个空的Java应用(这里我用了maven管理,习惯性上我是先建立项目再添加maven管理,你也可以直接创建maven项目)。我叫它Jxpress。这里我用的IDE是IntellJ。当然eclipse或者其他的IDE也可以。

有了一个新的工程之后,我们需要一个底层连接的库。这里我选用Jetty。

在pom.xml里面添加jetty的依赖。

<dependencies>
    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-server</artifactId>
        <version>9.3.11.v20160721</version>
    </dependency>

    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-util-ajax</artifactId>
        <version>9.3.11.v20160721</version>
    </dependency>
</dependencies>

然后选择maven -> download source,jetty就被包含在项目里啦。

第一行代码

处理好了整个工程依赖后,我们可以开始写代码了。

首先,我们的整个项目是要做一个webserver。那么,我们先来定义一下我们需要哪些方法。

listen:监听方法。这个方法告诉我们该监听哪个端口。

start/stop:开始,结束。这个方法用于启动/结束我们的server。

get:注册一个get方法,来处理get请求。

于是我们就有了如下的代码:

[code lang=”java”]
public interface WebServer {
WebServer listen(int port);
WebServer get(String url);
WebServer stop();
WebServer start();
}
[/code]

如果你好奇为什么我的方法返回类型都是WebServer,答案是:这样我可以通过多个get方式来注册不同的处理方法。现在我们还不处理controller(在不久的将来我们会处理的),而是用一个固定的方法来处理它。

有了这个接口后,我们就可以实现它了!下面我们用JettyServer来实现这个接口。

[code lang=”java”]
public class JettyServer implements WebServer {
private Server server;

protected int port = 80;
//用一个list记录所有注册的get方法的URL
List<String> getURL = new ArrayList<String>();

public WebServer listen(int port) {
this.port = port;
return null;
}

//每调用一次get,就在list里添加一个url
public WebServer get(String url) {
getURL.add(url);
return this;
}

public WebServer stop() {
// 如果server对象已经存在
if ( server != null){
try{
server.stop();
}
catch (Exception e){
// do nothing
}
}
return this;
}

public WebServer start() {
server = new Server(this.port);
try{
server.start();
}
catch (Exception e){
e.printStackTrace();
}
return this;
}
}
[/code]

 

敲完以上代码后,我们建立一个Main.java文件。

[code lang=”java”]
public class Main {

public static void main(String[] args) {
WebServer server = new JettyServer();
server.get("/").get("/introduce").listen(8081);
server.start();
}
}
[/code]

这样运行后,我们会看到如下图:

添加处理器

小伙伴们好奇了:我们不是添加了 “/”应该显示hello world么?

不错,逻辑上来说是这样没错,但是事实上,我们需要将我们的逻辑放在处理器里,并将JettyServer的处理器设为我们的处理器才行。

创建一个class 叫WebServerHandler,让它实现AbstractHandler.

[code lang=”java”]
private static class WebServerHandler extends AbstractHandler {

private List&lt;String&gt; getURLs;
public WebServerHandler(List getURLs){
this.getURLs = getURLs;
}
public void handle(String s, Request request,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse)
throws IOException, ServletException {
try{
process(s, httpServletRequest, httpServletResponse);
}
catch (Exception e){
e.printStackTrace();
}
}

private void process(String s,
HttpServletRequest request,
HttpServletResponse response)
throws IOException {
for (String each : getURLs){
// 如果请求的url 与任意一个get注册的url 相等
if (each.equalsIgnoreCase(s)){
// 在响应里输出hello world,并把状态字设为200(OK)
OutputStreamWriter optwriter = new OutputStreamWriter(
response.getOutputStream(),
"utf-8");
optwriter.write("Hello world!");
response.setStatus(200);
optwriter.flush();
}
}
}
}
[/code]

然后将start方法改成下面的样子:

[code lang=”java”]
public WebServer start() {
server = new Server(this.port);
// 生成处理器
Handler hdlr = new WebServerHandler(getURL);
// 将我们的处理器设为Jetty的处理器
server.setHandler(hdlr);
try{
server.start();
}
catch (Exception e){
e.printStackTrace();
}
return this;
}
[/code]

Hello World!

再次启动我们的服务器,然后访问一下:

Bravo!


本文完整代码在Github上有收录,链接为:https://github.com/andysim3d/JxpressTutorial