在知乎上有这样一个问题“为什么美国程序员工作比中国程序员工作轻松、加班少?”,问题描述如下:
感觉美国程序员工作时间灵活、加班少,相比与国内程序员工作,似乎压力小很多。但是美国程序员的产出却非常牛逼(如google、fb等)。难道是因为他们效率更高吗?如果是,国内程序员是否能提高效率减少加班和压力呢?
下面这个来自“invalid s”的回答获得近万个赞,一起感受一下吧。
01
是的,他们效率更高。
但是,国内程序员不可能通过提高效率减少加班和压力。因为这事的决定权不在你而在公司。
之前“开发和产品经理因为识别手机外壳颜色而打架”的传闻之所以能引起广泛共鸣,就是因为这类事实在太普遍了,太多人感同身受。
因为中高层傻。
所以,当你花大力气设计了一个精简高效的架构,把一个很难的问题干净漂亮解决掉时,绝不会有人击节赞叹——恰恰相反,他们觉得你捣鼓了个把月才产出几百行代码,反而会犯嘀咕:这人是磨洋工呢,还是不会?
你面向搜索引擎编程,乱七八糟拷一大堆东西到代码里,用到用不到都留着,KPI表现反而会特别亮眼。
一天几千行代码当然亮眼。
一群外行,怎么会知道这几千行里面就两行有效呢。
02
类似的,你兢兢业业,一个bug都不让出,人家就把你忘了;反之,你大大咧咧,一个功能你能写出800个bug——经理看起来就很忙很努力,因为他得不停的和你交流;你也很忙很努力,不停跑经理那里讨论问题:全公司你最忙你经理最敬业,不奖励你俩还有天理吗?!
你看,你好我好大家好,身为聪明人,你为什么不多写点bug呢。
当然了,这是极端情况。大多数公司还是没这么极端的——他们的中高层还不是纯2X。
即便如此,他们中的绝大多数——包括多数程序员——仍然不懂软件工程。
他们并不知道,或者说并没有想过,今天你写的每一行代码,都会是明天的新代码的地基。
即使你知道,也没办法让中高层明白。
如果你今天写的太过随意,明天就很难在这个基础上扩展它;如果你着急完成任务,今天不先把昨天的设计缺陷修改掉,而是想一个办法绕开……那么明天你就不得不绕着圈子躲开更多问题。
越往后,就越难改;越难改,就越容易出bug。
但是,如果你想改昨天的代码,你就得先解决前天的问题;想解决前天的问题,大前天乃至大半年前的设计缺陷你就得逐一解决掉。然后,这大半年里,你就完不成任何新提的需求。
反正至多做三两年我就要换工作了。随他去吧,完成眼前的工作要紧。
因此,为了急功近利的眼前效率,中国程序员的长远效率自然变得极低——越往后越低。
03
我曾经接过一个任务。
因为高层设计的严重问题,我们不得不在网络通信层去更新用户登录状态(稍微懂点的都知道这需求有多奇葩:打个比方的话,这就好像让发动机制造商在活塞上做一个阀门以便随时泄压一样怪异。原因是我们的整车商忘了装启动机也没有离合器,所以需要减轻发动机阻力方便人家把车推起来)。
项目经理不懂。他觉得一条SQL语句也就是0.0x秒的事,我们的流程耽误1秒问题应该不大,所以就答应了。
我说每个用户都可能卡这么0.0x秒,人多了咱这模块吞吐量就没法看了。这个咱不能接。真要接也行,得改成多线程架构,得多安排时间。
经理说没事,直接加就行。做出事了他们负责就是(言外之意,一旦接了这个,将来我们自己的锅也有办法拉他们一起来背)。
既然都这么说了,我就动手做。
做完,内部测试没有任何问题;但一上线,整个系统死了。
原因是,那个库负荷特别大,一条数据库更新语句能卡几秒甚至几十秒。将来人多了还会更卡。
经理说,算了,你改多线程吧。
我思考了三天,决定不动我们这边的架构;而是设计个thread_call接口。任何传给thread_call的函数都会在另外的线程里执行——为了避免读写到调用函数的局部变量、然后在线程执行时调用函数已退出,thread_call内部会自动申请内存,把转交给工作函数的字符串等通过指针引用的参数统统复制过去;当线程执行结束,函数返回值也会保存在某地等待查询(超时或查询后自动删除),同时释放用到的资源。
为了实现这个,需要一个全局单例类负责管理线程、及时清理用到的资源;同时最好有一个线程池和一个内存池,免得频繁申请/释放。不然长时间运行下去,把内存弄的千疮百孔,程序就更容易出问题了。
内存池我已经写过一个泛型版本,直接拿来用就行。剩下的线程池、资源自动申请/释放(基于RAII和泛型,不支持原始指针因为无法确认空间大小、也无法确保复制成功,玩过泛型的都懂),加起来一百来行代码解决。最终代码量300多点,其中一大半是注释。
这个东西轻松的一次编译通过;然后挺过了各种测试,没发现任何问题。
这东西差不多相当于给C做了个简易协程框架(当时协程概念还没流行起来,不然我就把yield也实现进去了),今后遇到任何类似的“需要并行工作、但又不涉及数据竞争”的需求,直接写个处理函数然后丢给thread_call执行就好。
你看,如果程序都照这样写,是不是就会越写越快?
因为你昨天写的东西,今天可以拿来就用。写的越多,积累越多,实现新功能时需要重新实现的东西就越少,效率自然越高。
04
但是这个东西让项目经理作了难。
这是因为,如果算KPI的话,等于我花一周写了300行代码;然后又测了一周……两周300行代码的产出,这实在太少了。
反观别人,一个用户注册,人家一个字段一个字段一个字节一个字节的用代码检查、复制,轻轻松松搞出来500行。很水的几个功能轻松灌水上万行代码,然后部门KPI也有了,个人重要性也体现了——而且修不完的bug:你看,离了我们这个部门,公司真不能过啊!
可我傻乎乎的300行代码搞出这么复杂个东西,竟然还测不出bug……项目经理是知道这里面功能多,但上面觉得你忽悠他。300行代码你还能吹出花来不成?
而且,既然没有bug,以后人家还需要你这个部门吗?问题都解决了,我们这些人……还有继续雇佣的必要吗?
总之,他希望以后再写程序,尽量写长一些……而且,为什么要复用呢?其实每一个类似的需求,都是可以给他整个几万行代码出来的嘛。
05
没错。人家的预期是:这是个挺复杂挺难的任务,你应该加班加点忙上几个星期,提交几千上万行代码,到时部门KPI有了个人业绩也好看——将来每个类似任务都应照此办理。
而我呢,轻轻松松300行代码,杜绝了类似任务的出现——什么都不用管,加一行thread_call,全都妥妥贴贴了。
一个任务对应一行,这KPI还能看吗?
你看,面向目标的不同,面向KPI编码就必然使得实现臃肿、问题频发、每天996过劳死……但做起来其实轻松愉快,因为你完全可以磨上仨月洋工,然后吹嘘“多线程有多难”;然后还能让高层不断找你、解决诸如野指针、数据脏读脏写、死锁、内存碎片导致长时间运行后大块内存分配失败等等等等疑难问题——既让你显得重要,又能轻轻松松“骗”来大量的KPI,最后还不需要去学鬼画符一样、难的不要不要的泛型技术……
而面向问题编码呢,借助泛型,自动识别、复制函数参数(它们可能来自调用者的栈,随时可能失效),再加上用池来加速资源回收/分配效率、提前杜绝内存碎片问题——这完全是个简单轻松解决的小模块。而且只需解决一次,我们自己的“类协程库”都出来了,以后写程序会越来越快、越来越好:你甭管我怎么做完的、耗了多少时间,功能点我给你实现了、上线后bug free,是不是对双方都有利?
06
但是,后者在这个公司行不通。
代码量少没KPI你气不气?
bug写的少没人找你显得你不重要,倒霉不倒霉?
将来项目失败抓人背锅时,别人说我天天加班996007态度端正;而你呢,955一分钟班不加,这态度是不是很能说明问题?
从上到下都不懂你能怎么的?
别说这家公司的管理者了,他们的技术人员自己都不懂。我两个关系比较好的同事,还真以为我们是公司里干活最少、最不重要的几个呢。
因为别人忙忙碌碌总有干不完的活、修不完的bug,高层中层领导天天围着转,求爷爷告奶奶但任务就是做不完,重要的不得了。而我们几个公司公认的技术专家呢,每天到时间就走;座位上冷冷清清,从无领导过问;经常上班时间闲极无聊于是借“学新技术”的名义逛论坛……
时间久了,他们自己都心虚:为啥别人总是有干不完的活、见不完的领导?为什么我们经常整周整周的没有任务、闲坐着发呆?人家是不是比我们干的多、任务难啊?不对啊,每次分配任务,分给我们的,都是别人接不了、不敢接的啊?
07
直到有一天,午饭后散步聊天打屁谈到这事,我才觉得不对,提议回去看看工作日志/提交记录之类东西。
那天我们大概照例聊到了下午三点吧——没错,因为事少,因为要都要不来工作,一个月至多也就忙一周,955都大块大块的空闲时间。别说加班了,平常上班我们都经常偷空出去散步。
悠哉游哉回到公司之后,我们就去翻看所有同事的提交记录和bug报告数据。这才惊讶的发现,我们比其他同事完成的功能点数量高出5~10倍、难度也普遍更高,bug率却近乎为0——别人一个功能点能有密密麻麻几十个bug,而且上线几年bug都抓不完;而在我们看来,这些都是压根就不应该发生的低级错误,而且我们提交的代码的确不包含这类错误。
所以,别人一年只做三四个功能点,每个功能点都要出十几、几十个bug;而我们呢,一年起码几十个功能点,加起来不过3~5个bug(我更是一年只有1个bug,而且bug原因还是需求没写清:某个字段让返回字符串,我按照C惯例后面加了个‘\0’;对方用的java,不能识别这个\0)。
08
问题是,“我们接的任务最多最难”,这事我们项目经理知道,中高层领导不知道。
中高层领导知道什么呢?他们只知道,这个任务总是在别人那里卡住;他们只知道,系统出了问题,该找的人肯定不是我们几个(从不出bug自然不需要找)——所以你猜,在他们心里,谁更重要?
09
于是我决定辞职。
这是我第一次进这种公司,也是最后一次。
因为这种公司完全是“逆淘汰”。水平越差越吊儿郎当越吃香,水平越高越兢兢业业越被边缘化。