返回信息流什么都不说了,看代码:
```go
// chinese.go
package main
import (
"fmt"
"unicode/utf8"
)
func askIgnorantAmerican(s string) {
l := len(s) // One byte is one character. What's so difficult?
fmt.Println("The ignorant American says:")
fmt.Printf(" I think '%s' is %d characters long\n", s, l)
}
func askTheRightPerson(s string) {
l := utf8.RuneCountInString(s) // 尼玛!得到字符串长度这么麻烦!
fmt.Println("The right person says:")
fmt.Printf(" 其实“%s”有%d个字符\n", s, l)
}
func main() {
shijienihao := "世界你好"
askIgnorantAmerican(shijienihao)
askTheRightPerson(shijienihao)
}
```
输出结果:
```text
$ ./chinese
The ignorant American says:
I think '世界你好' is 12 characters long
The right person says:
其实“世界你好”有4个字符
```
Go起了一个非常差劲的头:错误的做法如此简便,而正确的做法如此晦涩。好吧,你可以说,只要懂怎么用go写unicode程序,这些问题都可以解决。但是,**全世界大多数程序是一问三不知的美国人写的!**让不知道字符编码问题的人来写Go程序,十有八九会用`len(s)`来获得字符串的“长度”,这样bug就会无处不在。
再看看同病相怜的Python 2。因为程序员不知道unicode的重要性,大多数会选用str类型。然后到处都是unicode的bug。mercurial到现在还只支持Python2不支持Python3,他们正在被unicode坑。程序一开始没有好好处理unicode问题,现在Mercurial的Python3移植人员正在痛苦地挣扎,他们说:[Python 3.x has proven notoriously difficult to support, due to our pervasive dependence on a byte-based encoding strategy and string manipulation. We have invested a substantial amount of time into porting at this point and we estimate at least a person-year of core developer time to make a full 3.x-compatible version.](https://www.mercurial-scm.org/wiki/SupportedPythonVersions#Python_3.x_support.)。唉,早不用Python3,现在sb了吧。Python2[还有3年时间到死期](https://bbs.byr.cn/#!article/Python/14338),看你们还有几个person-year。
还有可怜的gEdit文本编辑器。它的code snippet插件是用Python2开发的,里面所有的字符串处理都是用str处理的,然后只要在snippet里加一个汉字,gEdit就会崩溃。不过好在有人报过bug,现在问题已经[通过修改8个.py文件进行40处删改而解决了](https://mail.gnome.org/archives/commits-list/2012-January/msg08963.html)。
不仅仅是Go,rust也是重灾区。rust选用了同样的策略。在rust语言里,`String.len()`也是返回*字节*数,而`String.chars().count()`才是*字符*数。甚至专门有一个博文来讨论这个问题: http://brandonio21.com/2016/04/rust-you-probably-meant-to-use-chars-count/
所以,怎么说呢,还是语言设计的失误吧,导致了这么多的麻烦。
Java算是重视Unicode非常早的语言了,String里的char就是unicode字符,在Java里,String.length()就是字符数。也可以通过编码生成byte[]数组,但一看就知道那是二进制字符数组,不是字符串。Python 3也把默认的str类型弄成unicode了(干得漂亮!)。我简直不敢相信2009-2010年出现的Go和Rust会在语言设计上犯这么低级的错误。
这是一条镜像帖。来源:北邮人论坛 / golang / #331同步于 2016/6/17
该镜像源已超过 30 天没有更新,可能在源站已被删除。
Golang机器人发帖
「我又来黑Go语言了」一问三不知的美国人写程序
nuanyangyang
2016/6/17镜像同步14 回复
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
【 在 nullne 的大作中提到: 】
: 平时尽量避免使用非英语语言,即使是注释 这样子省了很多麻烦
嗯。作为开发人员,用英语写代码、注释、文档,有利于国际交流。
但是毕竟程序要处理的数据还是国际化的。
```Python
func askTheRightPerson(s string) {
r := []rune(s)
fmt.Println(len(r))
}
```
【 在 nuanyangyang 的大作中提到: 】
: [md]
: 什么都不说了,看代码:
: ```go
: ...................
这需要涉及一次转换吧,O(n)复杂度的。
【 在 asm 的大作中提到: 】
: [md]```Python
: func askTheRightPerson(s string) {
: r := []rune(s)
: ...................
【 在 nuanyangyang 的大作中提到: 】
: [md]
: 什么都不说了,看代码:
: ```go
: ...................
Python2.x的编码问题比Go严重太多了。
unicode并不比utf8更好。
Go的编码设计这篇官博讲的很清楚:Strings, bytes, runes and characters in Go -> https://blog.golang.org/strings
楼主的问题像是在说:我在Java能这么写,为什么Go不能?
"""
To answer the question posed at the beginning: Strings are built from bytes so indexing them yields bytes, not characters. A string might not even hold characters. In fact, the definition of "character" is ambiguous and it would be a mistake to try to resolve the ambiguity by defining that strings are made of characters.
There's much more to say about Unicode, UTF-8, and the world of multilingual text processing, but it can wait for another post. For now, we hope you have a better understanding of how Go strings behave and that, although they may contain arbitrary bytes, UTF-8 is a central part of their design.
By Rob Pike
"""
Go的五十度灰 -> http://www.feedlynews.org/news/1141372/50-shades-of-go-traps-gotchas-and-common-mistakes-for-new-golang-devs
没法说对错的,都是trade off。
"""
Go is a simple and fun language, but, like any other language, it has a few gotchas... Many of those gotchas are not entirely Go's fault. Some of these mistakes are natural traps if you are coming from another language. Others are due to faulty assumptions and missing details.
"""
【 在 shmilyhx 的大作中提到: 】
:
: Python2.x的编码问题比Go严重太多了。
: unicode并不比utf8更好。
: ...................
问题并不是Java的习惯和Go的习惯的问题。问题是:**正确的事情应该简单,错误的事情应该复杂或者几乎不可能**。用Python的禅的说法就是:
> Although practicality beats purity.
> Errors should never pass silently.
> Unless explicitly silenced.
> In the face of ambiguity, refuse the temptation to guess.
> There should be one-- and preferably only one --obvious way to do it.
> Although that way may not be obvious at first unless you're Dutch.
既然文本界有byte、rune、character、east-asian width这么多概念,而且还有争议,那么,Go语言就应该提供`string.NumBytes`、`string.NumRunes`、`string.NumChars`、`string.EastAsianWidth`这些方法。程序员发现有这么多种长度而无所适从,就会去查文档,配合你贴的链接里的那个好文来说明真相,程序员会选用正确的方法的。
其实Java也没有很好地处理这个问题。utf16也有局限性,比如Java的StringBuilder.reverse()方法也要特殊处理surrgate pair问题。但它起码在一开始设计的时候考虑了对字符串进行一次抽象。
**高级语言应该提供对现实中的数据的抽象**。对于string来说,人类直接能想到的就是它有很多character(人可以辨认的字符符号)组成。所以,如果一定要有一个默认的`len(string)`的话,它返回的应该是character;而且slice的时候应该切到字符之间的缝隙而不是切出半个字符;而且这个len应该和正则表达式`"."`的匹配个数相同。否则的话,就应该像Python3那样称之为bytes,或者更Go风格一点,叫`[]byte`。
诚然,用utf32编码是很低效的。但是并不是没有解决方法。人们用utf8来编码,就是因为”大多数情况“下它很高效(对于汉语来说并不是。gb18030每个汉字只要2字节,而utf8要3字节,浪费了50%的空间)。可以说,utf8是一种”优化“。但**我认为,这个优化放到语言设计阶段,太早了**。
比如V8引擎内部有`SeqString`、`SeqOneByteString`、`SeqTwoByteString`、`SlicedString`等很多种表示方法,而顶层提供一个抽象的`String`类。所以,只要某个字符串碰巧只包含英文字符,那么它就可以用OneByteString来高效表示,有汉字的也可以用TwoByteString来表示,而从高层看来,JavaScript定义String为list of characters,不管内部实现的编码是什么。甚至,现代的编译器可以应用JIT编译技术,如果发现程序一直在使用同一种编码来处理字符串,可以在运行时把所有涉及的代码都特殊化(specialise)成一种表示方法,这样连virtual dispatch都省略了。(可惜Go目前只有AoT的实现)
Go其实已经开了一个好头:string是immutable的。既然不可变,内部用什么编码对操作的依赖性就已经很小了。”连接合并两个字符串“也可以直接地做(用rope结构)。
如果觉得朴素的定义无法解决”多个rune表示一个字符“的问题,也可以提供特殊的抽象。甚至可以禁止某些对字符串的看似无辜的操作,比如反转字符串。不管怎么样,必须提供一个让”一问三不知的美国人“也能写出对大多数语言都能工作得很好的API。随着人们对国际化认识的加深,人们对自然语言的处理应该越来越进步,而不是退回`[]byte`时代。