cloudaice

    技术人员的痛

    工作将近三年了,然而自己并没有成为当初自己希望的那个样子,只是因为期间经历的事情并不是自己之前可以预期的。经过几年的工作,也渐渐明白了为什么中国的开发人员年纪大了之后都不写代码,而是去转管理,而一旦转不了管理就会变得忧心忡忡。

    今天发了一条状态:“dota里有句话说的很好,没有输出环境,在牛逼的核也是白搭“。结合当下的工作环境以及之前几年的工作感想,发现这句话在很大程度上表达了技术人员的痛。自己的工作经历也算和大部分人有些不一样,从实习开始,加入当时一家火的一塌糊涂的C轮公司,跟着一个部分从零开始建设基础服务和设施,同时参与研发了世界范围内都少有的百万并发级别的服务,三年后这家公司的市值翻了10倍以上,而我却徘徊在离职的边缘,第一次经历了公司成长过程中的动荡和迷茫。因为在这个时候,技术和能力是次要的,你所在部门的业务是否核心是关键,你所在的团队是否在上升是关键。而悲剧的是我所在的部门并不是做核心业务的,说的直白点,仅仅是卖东西的。我所在的团队属于系统架构团队,说白了和业务没有直接关系。而且leader也是偏向于体验生活类型的,并没有太大的追求。其实曾经,我觉得这些都不是问题,我觉得用技术把事情做好就可以了,但是后来不得不逼迫我去面对这些问题,因为你会发现你做的事情没有被关注,你要做更深入的时候需要更多的资源却没有办法拿到。这个时候你才会发现这一切都是你需要面临的问题。离职之前我想了很久很久,还是选择做些纯粹的技术。当然我知道自己放弃了什么,很多的money(期权),一次可能让自己走上管理岗位的机会。年轻的时候总会做很多选择,有些选择是错的,有些选择是冲动的,有些选择是必然的。我想我当时的选择是那个时候必然的。离开之后,我自己总结了一下,有缘无份。我有幸进入了一家如日中天的公司,当时甚至成为了全世界未上市公司中估值最高的公司。但是却没有在这家公司的发展过程中获得同等回报。细节的原因自然也会有很多,总结起来而言,是没有遇到一位足够牛逼的Leader。记得有人说过一句话:有牛逼导师的人未必牛逼,但是牛逼的人都有个牛逼的导师。

    如果你想做一个纯粹的技术人员,研发出优秀的架构和服务,那么你需要一个有足够资源和能力的支持者,否则你没有话语权,做不了更多的事情,最后只能逼迫自己去拥有获取更多资源的能力,然后渐渐把自己变成了非技术研发人员,而去专研更多的“软实力”。而在中国当前的互联网环境下,想要有一个能够非常大程度支持你的leader,给你一个足够的输出环境,实在是太难了,等同与寻找一个知己。

    在我离职之后,又去了另一家被世人称赞势头很猛的C轮公司。准确地说,我没有在技术这个维度选择上完全Follow My Heart,这期间也是有很多原因,不细说,总结起来四个字:”天灾人情“。

    在现任的公司里体会着不一样的全新的技术风格,以及相对之前在技术层面上提升很多的整体的技术氛围,但是也正是由于这种众多人技术背景不一致,会引起很多技术认识上的隔阂。因为出于对技术本身的敬畏之心,我不好随意评判个人技术的优劣,这里使用隔阂这个词来描述。很多情况下,讨论问题的时候,众多人的想法不会聚焦到一个Point,最后不了了之。具体做工作的时候,每个人照样按照自己的思想去做,更有些人是只负责说不负责做的,此时我也深刻体会到 Talk is cheap, show me code 这句话的深意了,其实在实际工作中不仅仅是如此,有些人喜欢对其所不知的现实情况按照自己想当然的方式指指点点,然而在很多时候还会让人无法反驳,甚至在内心里开始怀疑自己是不是做的有问题。而做一样事情更加是需要师出有名,现有名义,有多大Title才可以开始做,而不是说这件事情对当前公司有价值就去做,而不用去想太多的其它的事情,把精力聚焦在真正需要做的事情上。因此,纯粹地做一个技术研发人员的梦是很难实现的,因为当你做一件事情的时候首先要师出有名,你得为这个名考虑良久才可以决定怎么去做。

    其实,我上面说的事情都是一个度的问题,过犹不及。举个例子:很多时候只看PPT写的好不好,这是恨糟糕的事情,但是也不能太崇尚简单,起码要有一张A4纸去描述。但是很少有公司可以建立这样恰到好处的文化,为纯粹的研发人员提供一个良好的输出环境。

    资源,名义,无休止的廉价讨论挡住纯粹研发人员的去路。

    谈谈字符编码的问题

    在使用Python进行Web开发的时候,早晚会碰到字符编码的问题,这里以一种比较通俗的方式深入讲解一下字符编码的原理。

    为什么需要编码

    这个问题比较简单,最直接的回答就是:因为计算机内部只能表示0或者1,再底层一点讲,就是电路的开与关。但是实际应用中,人类对信息的编码是采用文字和符号这种方式。因此,我们需要把0和1映射到人类日常需要的文字和符号,我们也把这种映射关系称作编码方式。

    都有哪些编码方式

    ASCII,UTF-8,GB2312,这些都是字符编码。对于程序员而言,比较关心的还是ASCII和UTF-8。

    先说说ASCII编码,ASCII采用一个字节进行编码的方式,首位是0,也就是说一共可以保存2的7次方,128种不同的符号。这个编码方式对于英文及其常用符号地区是够用了。但是随着发展,其它国家也要给自己的文字和符号进行编码。比如中国,就要给汉字进行编码。然而如果每个国家都给自己的文字和符号进行一次编码。那么最后就会变的无法管理,可能同一个二进制子节,在不同的地方出现不同的编码方式。随着互联网的发展,世界范围内的信息交流和沟通变的更加频繁。于是出现Unicode字符编码,这种编码出现的目的就是为了形成世界范围内通用的编码。这里说下Unicode的编码形式:Unicode最开始采用两个字节进行编码,也就是说最大能容忍的编码个数为2的16次方,65536个。这个数字基本上可以满足全世界的字符编码需求了。当然如果要编码中国的所有汉字,那肯定是不够的,但是编码通用还是足够了,后来Unicode编码长度扩展到3个到4个字节。因此,你只要记住下面这一点

    • Unicode编码方式是一种包含世界各种使用符号的编码集合。

    Unicode编码的出现,似乎已经解决编码这个难题,我们所有的字符编码都采用Unicode编码就好了。但是总是有历史包袱的,在这里不得不多说一句,在计算机的世界里,你渐渐会发现有很多疑难杂症,或者难以理解的知识点,有很多都是历史原因把整个问题变的复杂了。在Unicode编码方式出现之前,ASCII编码已经广泛使用一段时间了,于是,接下来要处理一个非常棘手的问题:要如何才能区别当前这个文件是采用了哪种编码呢?估计大家最直接的想法就是在每个文件的开头加一个标识符,表示它的编码方式就可以了。但是很显然我们不能把当前所有的文件回收回来然后打上标识符之后再统一发回去。于是对于Unicode编码方式也不能仅仅是针对它的编码集进行存储了。随着互联网的兴起,交流变的更加频繁,对同于统一编码的呼声越来越高,因此,是时候说说UTF-8了。

    UTF-8本身不算是一种编码方式,它仅仅是Unicode编码的一种存储方式(当然你也可以认为它也是一种编码方式,具体后面会谈到)。Unicode对世界范围内做了编码规范,也可以认为是一种编码的表,在这张表里面每个字符都有与自己对应的16个二进制位。但是对于英文字母而言,ASCII的一个字节编码方式已经足够了,如果都统一换成Unicode的编码方式进行存储,那么对存储空间的浪费是无法忍受的。于是UTF-8出现了,它是一种变长的Unicode编码实现方式。它和Unicode编码是一一对应的关系,具体实现如下

    对于ASCII编码能够搞定的字符编码,UTF-8也使用一个字节,最高位使用0,其余和ASCII编码一致。注意这里并不是UTF-8自己定义的编码方式,而是Unicode的编码规范,只是真正的Unicode编码会把前面的一个字节全部变为0。这是一个字节的情况,对于n个字节(前面说过UTF-8是一种变长的编码方式),其中第一个字节的前n位都是1,n+1位是0,后面的字节的前两位统一是10,剩下的没有提到的二进制位全部是这个符号的Unicode编码。根据上面的描述,我们可以注意到一个UTF-8的编码不会超过7个字节,但是这个长度也足够它编码世界范围内的字符了。因此UTF-8编码的存储方式往往如下

    0xxxxxxx
    110xxxxx 10xxxxxx
    1110xxxx 10xxxxxx 10xxxxxx
    11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    

    其中x可以填充具体字符的Unicode编码,下面举一个具体的例子,例如中文你好,其UTF-8编码如下

    >>> s = "你好"
    >>> s
    '\xe4\xbd\xa0\xe5\xa5\xbd'
    

    转换成二进制表示如下

    11100100 10111101 10100000
    11100101 10100101 10111101
    

    其UNICODE编码如下

    >>> s = u"你好"
    >>> s
    u'\u4f60\u597d'
    

    转换成二进制表示如下

    01001111 01100000
    01011001 01111101
    

    对照我上文对UTF-8编码的存储方式是一致的。

    乱码的形成

    上面讨论了UTF-8的编码实现方式,以及它和ASCII编码是如何和谐相处的。但是这里我们要开始谈谈中文编码,中文编码不少系统默认是采用GB2312编码,GB2312编码具体方式可以阅读相关资料进行详细了解,但是有一点,那就是GB2312编码与UTF-8编码并不是互斥的。也就是说当我们在不知道编码方式的情况下解码一段文本的时候,是无法判断该文本是什么编码的,于是只能靠猜测算法去猜测当前文本是什么编码,但是对于文本量比较小的时候,相同的编码,使用UTF-8和GB2312都可以解开,这个时候就可能会导致乱码的出现。例如“联通”这两个汉字的GB2312编码是 C1AA,CDA8,转换成二进制方式:11000001 10101010,11001101 10101000,这段编码恰巧符合URF-8的编码方式,可以把它当成两个双字节的UTF-8编码,这样就造成了乱码现象的产生。因此,总结来说,乱码就是因为历史原因导致没有统一的编码规范,程序在反解已编码的字符的时候没有按照同一种编码方式工作。

    Python中的字符编码问题

    在Python的源代码中,我们经常会在文件的开头部分看到这么一行文本

    # -*- coding: utf-8 -*-
    

    大多数Python开发人员都知道这是用来指定编码格式的,但是在这里我要告诉你,这行代码仅仅是用来指定当前代码文本使用UTF-8编码,方便Python解释器在读取代码文本的时候正确识别代码文本文件中的字符。但是同时你的编辑器在保存的时候也要使用UTF-8编码格式才可以,否则你在源代码文件标明是UTF-8编码,实际却使用了另外一种编码,这就是欺骗了,这是第一个问题,不过这个问题对于Python开发者来说,一般很少遇到。

    Python2.x中对于字符串有两种表示方法,strunicodeunicode更像是一个复杂类型,通常表示一个Unicode对象。而str则是一个基础类型,在Python中它也是基础的不能再基础了,它仅仅表示字符数组([]byte)。而大多数Python中的中文字符编码问题归根结底都是因为这个原因。

    'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)
    

    因为str仅仅是字符数组,因此,即使你把某一段中文使用UTF-8编码的时候,它的存在形式还是字符数组,当你使用len内置方法去求值的时候,你看到的肯定不是中文汉字的个数。这个时候怎么办呢?我们通常会把它转成unicode对象,然后进行操作,这样得到的结果就回是我们预期的。那上面的这段编码错误一般在哪些情况下会报出来呢?这里就要说到Python的隐式转换,当一个str类型变量和一个unicode类型变量进行连接操作的时候,或者对一个str对象使用encode方法的时候我,Python内部都会尝试将当前的str对象转换成unicode对象,然后在进行操作。那当把str对象转换成unicode对象的时候,采用什么编码呢?问题就在于此。它会采用sys.getdefaultencoding()方法返回的编码方式,很不幸的是,往往返回的是都是ascii。而实际上你的str对象里面保存的是UTF-8编码的字符数组,而Python默认却会使用ascii去转换,这个时候就报出上面的错误了。

    我们该如何去避免这种编码错误的问题发生呢?解决方法有几个,分别按照优雅方式列举一下。我们在Python进行开发的时候,对字符串统一使用unicode对象来表示,尤其是带中文的字符串,千万不要使用str类型,这样就从根源上避免了Python隐式从str转成unicode的可能性。对于外部传递进来的参数,尤其是网络调用传入的参数,必须先转成unicode类型再进行后续的操作。这种方式比较干净,纯粹。

    还有一种方式,就是在Python源码文件的开始处加上下面三行代码

    import sys
    reload(sys)
    sys.setdefaultencoding("utf-8")
    

    这种方式会改变Python默认从str转成unicode采用的编码方式。只要保证我们的中文字符统一采用UTF-8编码方式,这种方式也能很好解决字符编码问题。但是每个文件总是写上这三行代码看上去会比较dirty。还是第一种方式比较彻底,干净一些。

    总结

    Python的中文编码问题

    1. 源代码文件需要指定文本的编码方式
    2. Python的str类型仅仅是字符数组,同时还有一种unicode类型用于表示Unicode字符串,而在对str进行某些操作的时候,Python会隐式地将str转成unicode对象,而这个转换的编码方式是依靠sys.getdefaultencoding()的返回值
Fork me on GitHub