2014年4月月 发布的文章

即使别人是码农,你却不该是

好几天前,在微信里,有个童鞋给我留了这么一段话:
「程序君,昨日知乎日报上出现的那篇《为啥中国的程序员都被称为码农》(以下简称「码农」),看完实在心酸,作为一名还在大学校园即将走向“码农”大军的愣头青,想请教您,你对那篇文章有啥看法?上面的说法属实吗?中国程序员的现状大体是怎样?麻烦指点」
我大概看了一下那篇文章,说的有些道理。但程序君认为:别人是不是码农与你无关,你不该成为那篇文章作者眼中的码农。作者说码农一词强调程序员「地位低下、枯燥和劳累」。作为一个程序员,我也来随便说说。
收入和地位
一般而言,程序员的收入水平不低。我没有具体的数据,但在一线城市,程序员的平均收入应该都能达到该市的中上水平 —— 我猜top 30%左右。2012年,我们在校园招聘的时候,很多面试后非常心仪的同学(清华,北大,中科院等)最终都拿着十几到二十万的薪资去了B,T等公司,有一个我们特别中意的iOS工程师,被我们追了很久,但后来最终还是被某著名游戏公司招安,拿了二十好几万的薪水 —— 这可是程序君工作了好几年后才能拿到的package啊。
所以你说程序员的收入低么?为什么你的收入会低?为什么你怕你未来的收入会低?
程序君有个朋友,也是途客圈的前员工,本科来实习前已经有很多独立的项目经验,掌握了python/django和iOS的开发能力。他聪明好学,上手能力非常快,稍加指点就能从事重要功能的开发,勤奋程度又不输于程序君,所以进步神速。后来途客圈的很多核心功能都交由他来负责,某些功能程序君都自认为无法做得比他好。一年半后他从途客圈「毕业」时,已经是各大公司争相想招致麾下的「面霸」,同时拿到了好几份offer,最终去了某搜索公司,现在前途一片光明。
我想这是一个很有借鉴意义的例子,尤其对于在校学生。就像之前的『软件开发升级打怪之路』所讲的那样,我们身边有那么多很有意思的问题可以通过软件来解决,你愿意放弃一部分打dota的时间和精力去解决么?你愿意在解决的过程中排除万难,啃下一个个硬骨头么?
如果你在学生时代就有很多拿得出手的项目,那么在现在互联网热火朝天,人才缺口很大的时代,找一份薪水不低的工作还是难事么?
程序员可能是世界上唯一一份不用太靠学历,不用太靠爹娘,甚至都不用太靠熬日子出头的工种。有人杜撰了这么个故事:
说Python之父Guido van Rossum有天跑去google面试,说了三个词:”I wrote python”,就被录用了。
当然像Guido这样的大牛犯不着主动去找工作的,就像球场上的超级明星,给猎头打个电话,说我想挪个窝了,工作机会就会像雪片一样飞来。这个故事虽为杜撰,但不失一个很好的例子说明程序员「不靠天,不靠地,就靠自己一双手」的本质。你的薪水取决于你能做出什么来。
至于地位,我觉得除了权贵阶层,其他人的地位都差不多。如果不做公务员,没事别老琢磨地位,那玩意说来就来,说走就走。我倒觉得程序员应该多提高自己的品味 —— 至少多学学打扮自己,别拿着中产的收入过得像无产阶级。
另外,建议妹子们也多关注关注程序员这个群体 —— 毕竟能够改变世界的几类人中,程序员算是最好接近,在比较年轻的时候就能看出潜力,也最好玩弄于股掌中的。^_^
工作枯燥
工作枯燥这事真心和你自己的感受有关。首先不是所有人都适合做程序员的,如果你换了不少团队或公司,做什么都觉得枯燥,自己又没兴趣做pet project,那你要好好考虑下自己是否适合这条路。否则走下去,就真成了「码农」一文中的码农了。
有人曾经给我留言说自己不想做业务相关的事,没意思,想做「真正的程序员」做的事情。拜托,我们做的是产品,哪个产品不是和业务相关的呢?脱离了业务的软件,要么是纯粹个人爱好,要么只能在象牙塔里生存。
有人说工作特么没劲,每天干的都是琐碎边缘的活儿,枯燥死了。好吧,你以为程序君做得总是高大上的事情么?程序君最近两周干的活也琐碎得要命,其中一个任务类似于「从linux kernel的源代码里,把所有.c引用的.h文件摘一摘,只留下真正有用的(但系统还能正常编译运行)」。
工作中这样的活不少,枯燥是有点枯燥,遇到了与其怨天尤人,不如想办法快点将它完成。程序君花了大半天时间,在走了很多弯路写了两个程序后,终于找到一个巧妙的办法,仅仅写了五十多行python代码就将其完成。效果从最初的方案减少了25%的.h文件一直到减少了95%的.h文件。
我比较不理解有程序员说自己总不得不做重复劳动,所以感觉工作异常枯燥。想想「程序员」这顶帽子带在头上意味着什么?它意味着全世界任何群体都有理由说自己的劳动是重复劳动,唯独程序员这个群体不能。为何?程序员坚守的信条是DRY(Don’t Repeat Yourself),一件事当你发现你需要重复第二次时,就要考虑将其自动化。做不到这一点的请努力,因为这决定了你的效率和效能。
还举我自己的例子吧。前些日子我要测试几个开发环境,流程大概是下载代码,编译,运行UT。因为开发环境有点问题,所以在下载完代码后我需要对代码打个patch。这活第一遍我是手工做的,为了验证整个流程的正确性,调整patch等等。第二遍以后我就写了个脚本将其自动化。虽然在我写这个脚本的时间里,我完全可以对所有的开发环境都一一验证,但脚本化的好处是,我可以让别人用这个脚本也进行独立验证,我也可以在今后几天的工作中反复使用。
枯燥是你看待任务的主观情绪。很多看起来外表光鲜的互联网公司或者软件公司,真正分到你手上的任务就不见得光鲜靓丽。大数据?那是对外美好的商业表述。你真正做的事情也许是对海量日志进行或手动或半自动分析,枯燥不?操作系统?好吧,你去了以后发现主要做的是本地化,枯燥不?虚拟化?好吧,那里很大的团队在做驱动开发,枯燥不?
没那么多枯燥。软件就是一个个实现起来非常枯燥的功能有机地组合在一起,为用户(客户)提供价值。无法认清这一点,总认为自己干的就是最枯燥的,那你只能继续枯燥下去,也只能成为「码农」作者眼中的码农。
辛苦劳累
辛苦劳累倒是真的。不过要看你怎么个辛苦法。
如果你在一家各种限制你自由发挥,还以你工作时长为工作态度和工作能力的评定标准,那么,除非你有其它想法,否则应该选择离开。记得我毕业后工作的第一家公司,有天晚上吃饭,老板问我对team里两个女孩有什么评价,我说她们工作得挺好,合作愉快啊(潜台词是男女搭配,干活不累^_^)。老板努了努嘴,说:可她们一下班就回家,工作态度不积极啊。我听着不是滋味,心里就萌生了离开的念头。
程序员的工作绝对不应该用工作时间,是否加班来衡量。如果你的老板给你的评定是「该员工工作积极努力,主动加班,blablabla」,你还愿意这么呆着,那你就别抱怨辛苦劳累。
不过现状的确是是很多程序员都在加班,包括我在内。
有些人加班是真忙。但其实有很多行业比程序员忙得多,比如四大所在的会计(审计)行业,比如投行,咨询。
也有些人加班是刷存在感。
但更多的人加班是为了有一个清静的环境,能做点什么。
要说辛苦劳累,我觉得一个很重要的原因是:这个工种需要你不断更新夯实自己的技能。
如果被迫接受,那身心俱疲;如果主动出击,身体累了点,心灵上的成就感还是不小的。
http://blog.sae.sina.com.cn/archives/3548

AngularJS开发的理念

Angular信奉的是,当组建视图(UI)同时又要写软件逻辑时,声明式的代码会比命令式的代码好得多,尽管命令式的代码非常适合用来表述业务逻辑
将DOM操作和应用逻辑解耦是一种非常好的思路,它能大大改善代码的可调性;
将测试和开发同等看待是一种非常非常好的思路,测试的难度在很大程度上取决于代码的结构;
将客户端和服务器端解耦是一种特别好的做法,它能使两边并行开发,并且使两边代码都能实现重用;
如果框架能够在整个开发流程里都引导着开发者:从设计UI,到编写业务逻辑,再到测试,那对开发者将是极大的帮助;
“化繁为简,化简为零”总是好的。
AngularJS能将你从以下的噩梦中解脱出来:
使用回调:回调的使用会打乱你的代码的可读性,让你的代码变得支离破碎,很难看清本来的业务逻辑。移除一些常见的代码,例如回调,是件好事。大幅度地减少你因为JavaScript这门语言的设计而不得不写的代码,能让你把自己应用的逻辑看得更清楚。
手动编写操作DOM元素的代码:操作DOM是AJAX应用很基础的一部分,但它也总是很“笨重”并且容易出错。用声明的方式描述的UI界面可随着应用状态的改变而变化,能让你从编写低级的DOM操作代码中解脱出来。绝大部分用AngularJS写的应用里,开发者都不用再自己去写操作DOM的代码,不过如果你想的话还是可以去写。
对UI界面读写数据:AJAX应用的很大一部是CRUD操作。一个经典的流程是把服务端的数据组建成内部对象,再把对象编成HTML表单,用户修改表单后再验证表单,如果有错再显示错误,然后将数据重新组建成内部对象,再返回给服务器。这个流程里有太多太多要重复写的代码,使得代码看起来总是在描述应用的全部执行流程,而不是具体的业务逻辑和业务细节。
前得写大量的基础性的代码:通常你需要写很多的基础性的代码才能实现一个“Hello World”的应用。用AngularJS的话,它会提供一些服务让你很容易地正式开始写你的应用,而这些服务都是以一种Guice-like dependency-injection式的依赖注入自动加入到你的应用中去的,这让你能很快的进入你应用的具体开发。特别的是,你还能全盘掌握自动化测试的初始化过程。

Java异常处理步骤

Java异常处理步骤:
第一句一般是总的,告诉你哪里错了,错误就在caused by后面的语句提示里面,或者是自己写的类里面。异常信息是从上往下抛(大概的说法)的,因为下面是调用上面的方法,也就是说第一行是最终抛出异常的方法,而最后一行最上层调用的方法
异常链是一种机制,异常转译时,保存原来的异常,这样当这个异常再被转译时,还会被保存,于是就成了一条链了,包含了所有的异常,所以你可以看到这样的异常打印:

org.apache.phoenix.exception.PhoenixIOException: org.apache.phoenix.exception.PhoenixIOException: 系统找不到指定的路径。
	at org.apache.phoenix.util.ServerUtil.parseServerException(ServerUtil.java:107)
	at org.apache.phoenix.iterate.ParallelIterators.getIterators(ParallelIterators.java:527)
	at org.apache.phoenix.iterate.MergeSortResultIterator.getIterators(MergeSortResultIterator.java:48)
	at org.apache.phoenix.iterate.MergeSortResultIterator.minIterator(MergeSortResultIterator.java:63)
	at org.apache.phoenix.iterate.MergeSortResultIterator.next(MergeSortResultIterator.java:90)
	at org.apache.phoenix.jdbc.PhoenixResultSet.next(PhoenixResultSet.java:734)
	at com.youku.service.VideoFingerprintServiceNew.getSimlarVideoMD5(VideoFingerprintServiceNew.java:111)
	at com.youku.service.VideoFingerprintServiceNew.excute(VideoFingerprintServiceNew.java:50)
	at com.youku.service.AbstractHttpImpl.getResponseModel(AbstractHttpImpl.java:22)
	at com.youku.web.servlet.CommonHttpServlet.doPost(CommonHttpServlet.java:79)
	at com.youku.web.servlet.CommonHttpServlet.doGet(CommonHttpServlet.java:41)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:723)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:861)
	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:612)
	at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:503)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.util.concurrent.ExecutionException: org.apache.phoenix.exception.PhoenixIOException: 系统找不到指定的路径。
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:202)
	at org.apache.phoenix.iterate.ParallelIterators.getIterators(ParallelIterators.java:523)
	... 23 more
Caused by: org.apache.phoenix.exception.PhoenixIOException: 系统找不到指定的路径。
	at org.apache.phoenix.util.ServerUtil.parseServerException(ServerUtil.java:107)
	at org.apache.phoenix.iterate.SpoolingResultIterator.(SpoolingResultIterator.java:129)
	at org.apache.phoenix.iterate.SpoolingResultIterator.(SpoolingResultIterator.java:74)
	at org.apache.phoenix.iterate.SpoolingResultIterator$SpoolingResultIteratorFactory.newIterator(SpoolingResultIterator.java:68)
	at org.apache.phoenix.iterate.ChunkedResultIterator.(ChunkedResultIterator.java:90)
	at org.apache.phoenix.iterate.ChunkedResultIterator$ChunkedResultIteratorFactory.newIterator(ChunkedResultIterator.java:70)
	at org.apache.phoenix.iterate.ParallelIterators$2.call(ParallelIterators.java:631)
	at org.apache.phoenix.iterate.ParallelIterators$2.call(ParallelIterators.java:622)
	at java.util.concurrent.FutureTask.run(FutureTask.java:262)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
	... 1 more

这个异常链中就是包含了两个异常,最前面是顶级异常,后面再打印一个Cause by,然后再打印低一层异常,直到打印完所有的异常。
异常链,在JDK1.4以后版本中,Throwable类支持异常链机制。Throwable 包含了其线程创建时线程执行堆栈的快照。它还包含了给出有关错误更多信息的消息字符串。最后,它还可以包含 cause(原因):另一个导致此 throwable 抛出的 throwable。它也称为异常链 设施,因为 cause 自身也会有 cause,依此类推,就形成了异常链,每个异常都是由另一个异常引起的。
通俗的说,异常链就是把原始的异常包装为新的异常类,并在新的异常类中封装了原始异常类,这样做的目的在于找到异常的根本原因。
这篇文章很好:http://my.oschina.net/yanquan345/blog/204498
在我查过的资料中,以《Effective Java》书中对异常处理设计的研究得最系统,本文很多思想来自于它,下面我把其中的几条原则翻译(非直译)并贴上:
第57条:只对异常情况使用异常。(说明:即不要用异常处理控制正常程序流)。
第58条:对可恢复异常使用编译时异常,对编程错误使用运行时异常。
第59条:应避免不必要的编译时异常:如果调用者即使合理的使用API也不能避免异常的发生,并且调用者可以对捕获的异常做出有意义的处理,才使用编译时异常。
第60条:应偏好使用自带异常
第61条:抛出的异常应适合本层抽象(就是上面说的转译)
第62条:把方法可能抛的所有异常写入文档,包括运行时异常
第63条:用异常类记录的信息要包含失败时的数据
第64条:力求失败是原子化的(解释:就是如果调用一个方法发生了异常,就应该使对象返回调用前的状态)
第65条:不要忽略异常