九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

字符串的多国语言支持解决方案 通用解决方案篇

在用Qt的时候,自然是使用Qt的解决方案,简单易用,参考《字符串的多国语言支持解决方案 Qt篇》。

    在不需要跨平台开发iPhone的应用时,自然使用iOS提供的多国语言支持解决方案
    但是,不能用Qt,也不是开发纯iOS应用的时候呢?那就只能自己想个办法解决这个问题了。

根据实际情况,该解决方案需要符合下面的条件:
1.代码中使用时,不应该有太多额外的负担,不降低代码的可读性
2.不用单独的工具也能使用

第一条很好解释,假如为了多国语言而浪费太多的精力实在不值得,所以这里放弃更加高效的int编码索引字符串的方式,那种方式的确更加高效,但是代码中需要使用宏/常量来索引字符串。
第二条就完全处于开发简单的考虑,不要神马分析,生成等乱七八糟的东西,所以也不会如很多解决方案一样使用啥excel,然后通过工具解析成二进制的格式,然后程序中去解析二进制数据,那不是自虐吗?
那么方案其实就慢慢出来了,以文本配置来存储多国语言的文字,一种语言一个文件。在文本配置的格式选择上,使用json。用字符串Key来索引字符串,索引失败时,就直接显示Key字符串。

首先,json的格式就是最简单的以key为索引的字符串组合,比如,我现在建立一个en.json表示英文,一个cn.json表示中文。


然后实现如下StringManager,该类为Singleton:(用jsoncpp为json的解析库)
头文件:
class StringManager : public Singleton<StringManager>
{
public:
  bool Init(const char* filename);


  std::string GetLocalizedString(const char* key);


private:
  Json::Value string_map_;
};


部分实现:


bool StringManager::Init( const char* filename ) {
  if ( !ReadJsonFromFile(filename, string_map_) ) {
    return false;
  }


  return true;
}


std::string StringManager::GetLocalizedString( const char* key ) {
  if (string_map_.isMember(key)) {
    return string_map_[key].asString();
  }
  else {
    return std::string(key);  // 当查找不到key时,直接显示key
  }
}


一般情况下,直接通过StringManager的GetLocalizedString函数来获取字符串即可,为了更加简单,定义如下的宏:
#define LS(key) StringManager::Instance()->GetLocalizedString(key)

使用时,先需要以字符串的配置文件名初始化StringManager,读取字符串信息。以后,使用起来就和Qt中很类似了。即以LS()方式包含你需要显示的文字。
比如下面这样,为了减少其他无关信息,就没有添加显示部分的代码了:
 
在以上的例子中,我是使用utf8来保存多国语言,假如你是使用UTF16的话,请将相应的字符串表示改为宽字节即可。
 
小结:
因为没有额外的工具支持,这样的方式也许没有qt,iOS里面那么便捷,但是实现简单,容易理解,同时使用起来也足够的方便,最最重要的是,除了C++编译器,这套方案不依赖于平台或者其他神马东西,你随时随地都可以使用。(本例中用jsoncpp解析json,jsoncpp也仅依赖C++编译器存在)

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- blog.jtianling.com

阅读全文....

字符串的多国语言支持解决方案 Qt篇

    最近比较懒,公司的事情忙完后,在家也就是看看《Game Engine Architecture》,好久没有写博客了,总算遭到报应了,昨晚腹泻,发烧,冷汗,今天一天班都没上,于是,闲话少说,还是写篇博客吧.......

    在不久以前,软件还是由一帮根本不知道世界上存在其他语言的美国人制作的,那时候他们只用ASCII编码去写软件。然后当他们发现世界上还有其他语种的人也需要使用软件,并且也有很大市场以后,出现了多字节的解决方案来解决字符串的国际化问题,但是那是段相当恐怖的日子。再然后,我们有了Unicode,一切就简单了很多。

    假如一个软件只支持中文,那么简单的使用unicode的中文去表示UI中的所有字符串就好了,但是要支持多国语言呢?具体说来怎么样才能方便的在不同语言中进行切换呢?

    Qt的强大程度在很多方面都远超一个framework应该呆的范围,基本上是一个强大的跨平台解决方案,其中,对于多国语言,Qt的解决方案也是我见过的最好的。

对于Qt的字符串来说,分成两种情况:
1.在Qt Designer中拖放控件时,控件上的字符串。比如,我摆一个label上去,叫做hello world。注意的是需要在translatable属性上打勾(默认就是打勾的),表示可以翻译。
 


2.在代码里面直接指定的字符串,需要用tr()包含该字符串。比如手动创建一个label,显示Hello World Again!
  QLabel *label = new QLabel(this);
  label->setText(tr("Hello World Again!"));
  label->setGeometry(100, 100, 200, 25);


此时整体程序的显示内容如下:



此时,通过Qt菜单中的(用了qt的Qt Visual Studio Add-in)的Create New Tranlation File,


比如,这里我建了一个中文的文件,叫做qt_linguist_test_zh.ts,在VS中双击此程序,会用Qt Linguist打开此文件,接下来的就简单了:

分别在左边选择字符串所在的context,然后在Strings里面会列出所有可以进行翻译的字符串,在Sources and Forms中甚至还能显示出上下文,帮助你进行翻译。

在下面的translation中写上你想翻译的内容,保存好。

在VS中,用lrelease解析(编译?)此文件

此时,可以在工程目录下看到一个叫做qt_linguist_test_zh.qm的文件,就是刚刚生成的文件。在代码中使用该多国语言的文件实在是简单了,只需要下面几行代码:

    QApplication app(argc, argv);


    QTranslator translator;
    translator.load("qt_linguist_test_zh.qm");
    app.installTranslator(&translator);

从此以后,所有的字符串都会按照你翻译过的来显示:


小结:
Qt的多国语言支持主要来源于Qt Linguist这个翻译程序,按照Qt本身的设计,这个程序甚至是交由翻译人员去使用的,和程序员无关,程序方面只需要记得在代码里面的字符串加tr()就行,然后通过lupdate(在上面的例子中用Qt Visual Studio Add-in来完成了)去提取代码中所有可以翻译的字符串,生成ts文件,然后把ts文件交给翻译人员使用即可。其方便性在于不仅是程序员使用方便,还从软件开发流程上让各个环节都有合适易用的工具去高效的完成各自工作......作为程序员,开发一个Qt的多国语言支持的软件几乎没有任何额外的负担..............


原创文章作者保留版权 转载请注明原作者 并给出链接

九天雁翎(JTianLing) -- www.jtianling.com



阅读全文....

Google C++ Style中允许使用的Boost库(1)

write by 九天雁翎(JTianLing) -- blog.jtianling.com

新浪微博 -- 讨论新闻组 -- 代码库 -- 豆瓣



前言

作为系列的第一篇,如同往常一样唠叨几句吧,好久不写这种单纯语言相关的(特别是C++)文章了,因为慢慢觉得这些东西自己学学就OK,实际写出来的价值有限,因为思想少,技巧/知识多。因为前段时间做了半年多的Object C和JAVA了,并且C++ 0x标准就要出来了,语言改变还挺大,趁这个节骨眼,顺便再回头学习/总结一些我感兴趣的C++知识吧,不过应该持续时间不会太长,这个系列也不会太长,因为语言已经不是我关注的重点~~~~
Google的C++ Style Guide
是我自己写东西的时候遵循的C++代码风格规范,前段时间看到李开复说他才发现Google的C++规范已经公开了,说这是世界上最好的C++规范,我感到很惊讶,因为N年前这个规范已经就公开了-_-!事实上,Google的 C++ Style Guide远不仅是一个传统意义上的代码书写风格指导,对于C++的方方面面做出了Google的解释和使用建议,包括每个规则给出时,较为详细的讲了这个规则好的一面和不好的一面,最最激进的规则甚至有禁用C++的异常,以及除了Google规范的Interface作为基类外,禁用多重继承,在绝大部分情况下禁用默认参数等内容。在很大程度上,Google是想把C++打造成效率高的JAVA来使用~~~~
Google的C++ Style Guide
有关于Boost的一节,允许使用的Boost库如下:

Call Traits
 from boost/call_traits.hpp
Compressed Pair
 from boost/compressed_pair.hpp
Pointer Container
 from boost/ptr_container except serialization and wrappers for containers not in the C++03 standard (ptr_circular_buffer.hpp and ptr_unordered*)
Array
 from boost/array.hpp
The Boost Graph Library (BGL)
 from boost/graph, except serialization (adj_list_serialize.hpp) and parallel/distributed algorithms and data structures (boost/graph/parallel/* andboost/graph/distributed/*).
Property Map
 from boost/property_map, except parallel/distributed property maps (boost/property_map/parallel/*).
The part of Iterator that deals with defining iterators: boost/iterator/iterator_adaptor.hpp, boost/iterator/iterator_facade.hpp, and boost/function_output_iterator.hpp

对此我感到比较惊讶,除了Array没啥好疑问的以外,我发现一些的确很好用的Boost库Google并不允许使用,比如boost::bindboost::functionboost::lambda 等,这个我不理解~~~~而Google提及的几个Boost库,除了Array很简单实用,BGL是一个数据结构和算法的扩充库,以前没有接触不奇怪外,其他的东西我发现自己竟然没有太接触过,作为自认为C++学习已经接近语言律师的我情何以堪-_-!~~~~~

因为很多时候,一个Boost库就代表着一个C++的缺陷的补救,因为即使最后不用Boost库,了解一下对于怎么正确的使用C++还是有很大帮助的。特作此系列。
1.Call Traits from boost/call_traits.hpp

先谈谈什么是Traits,BS的解释如下:

Think of a trait as a small object whose main purpose is to carry information used by another object or algorithm to determine "policy" or "implementation details". - Bjarne Stroustrup
可以参考这里。所谓Call Traits就是调用时需要的Traits。 Call Traits中文文档看下基本就明白啥意思了。我感觉最大的作用是在写模版类/模版函数传递参数时,保证没有“引用的引用”的情况发生,并且总以最高效的形式传递参数。所谓的最高效形式的规则类似JAVA,(仅仅是类似)即原生的类型就使用传值方式,对象就采用传引用方式。这里有个中文的例子
正常情况下,一个函数在C++中要么以传值方式传递参数,要么以传引用的方式传递,没法两者兼得:
template <class T>
class TestClass {

public:

  TestClass(T value) {


  }


  TestClass(const T& value) {


  }
  
  T value_;
};
在使用时会报错:
error C2668: 'TestClass<T>::TestClass' : ambiguous call to overloaded function
因为C++的函数重载规则并没有规定在这种情况下会调用哪一个函数,导致二义性。
使用Call_Traits的param_type作为参数类型时,以下例子:
int g_i = 0;
class PrintClass {
public:
  PrintClass() {
    printf("PrintClass created");
    ++g_i;
  }
};


template <class T>
class TestClass {
public:


  TestClass(typename boost::call_traits<T>::param_type value) : value_(value){


  }
  T value_;
};


  TestClass<int> test(10);


  PrintClass printClass;
  TestClass<PrintClass> testPrintClass(printClass);


g_i会等于1,实际因为传递的typename boost::call_traits<T>::param_type value在参数类型是PrintClass(一个对象)时,传递的是引用。同时,我没有想到更好的办法去验证在传递的参数是int类型时,的确是通过时传值。这样说来就很有意思了,因为即使我们在使用模版时函数全部通过传值方式来设计,会在T是对象时导致很大的额外开销,我们全部通过const T&的方式来传递参数就好了,就算是原生类型,这种额外开销还是小到足够忽略不计的,只是,boost库的制作者觉得这样还是不够完美?
同时,Call Traits还解决一个问题,那就是"引用的引用",比如上例中T为T&时的情况..........函数参数假如是通过传递引用的方式的话,const T&的参数,T又等于T&,那么就是const T&&了,C++中没有引用的引用这种东西的存在(只有指针的指针),事实上,Call Traits给函数的调用和参数的类型有完整的一套解决方案,如boost文档中的example 1:
template <class T>
struct contained
{
  // define our typedefs first, arrays are stored by value
  // so value_type is not the same as result_type:
  typedef typename boost::call_traits<T>::param_type       param_type;
  typedef typename boost::call_traits<T>::reference        reference;
  typedef typename boost::call_traits<T>::const_reference  const_reference;
  typedef T                                                value_type;
  typedef typename boost::call_traits<T>::value_type       result_type;


  // stored value:
  value_type v_;


  // constructors:
  contained() {}
  contained(param_type p) : v_(p){}
  // return byval:
  result_type value() { return v_; }
  // return by_ref:
  reference get() { return v_; }
  const_reference const_get()const { return v_; }
  // pass value:
  void call(param_type p){}


};



2.Compressed Pair from boost/compressed_pair.hpp
这里正好找到一个很perfect的文章,简单的说就是当pair中某个类是空类时,compressed Pair比std中的pair会更省一些空间(1个字节...........),我几乎没有想到我实际工作中有什么对空间要求非常高并且还会使用pair的情况.................这也就是compressed_pair的尴尬之处了。可以稍微提及的是,看看compressed pair的定义,就能看到call traits的使用:
template <class T1, class T2>
class compressed_pair
{
public:
  typedef T1                                                 first_type;
  typedef T2                                                 second_type;
  typedef typename call_traits<first_type>::param_type       first_param_type;
  typedef typename call_traits<second_type>::param_type      second_param_type;
  typedef typename call_traits<first_type>::reference        first_reference;
  typedef typename call_traits<second_type>::reference       second_reference;
  typedef typename call_traits<first_type>::const_reference  first_const_reference;
  typedef typename call_traits<second_type>::const_reference second_const_reference;


  compressed_pair() : base() {}
  compressed_pair(first_param_type x, second_param_type y);
  explicit compressed_pair(first_param_type x);
  explicit compressed_pair(second_param_type y);


  compressed_pair& operator=(const compressed_pair&);


  first_reference       first();
  first_const_reference first() const;


  second_reference       second();
  second_const_reference second() const;


  void swap(compressed_pair& y);
};


说实话,虽然逻辑上感觉完美了,但是代码上还真是累赘...........typedef简直就是C++强类型+类型定义复杂最大的补丁工具.............但是总的来说compress pair是很简单的东西,不多讲。

3.Array from boost/array.hpp
Array也是最简单的boost库使用类之一了,用于以最小性能损失替代原生C语言数组,并且像vector一样,提供使用的函数和合理的封装(STL提供的vector因为是变长数组,还是有一定的性能损失)感觉不是非常非常效率要求的工程,可以将所有的C语言数组都用Array来代替,意义更加明确,迭代器使用也会更加方便,容器的使用语法也更加统一。另外,C++0X已经确定添加array库,array将来就是未来的标准库,可以较为放心的使用,并且即使使用了,也是可维护的代码(即使将来使用C++0X时也是一样)。
操作示例:
  boost::array<int, 100> intArray;


  intArray.fill(10);


  for (boost::array<int, 100>::iterator it = intArray.begin();
    it != intArray.end(); ++it) {


      *it = 20;
  }



小结:
基本上,
1.call traits是看需求了,假如你实现模板库有需要才使用,不要因为真的仅仅为了一个函数的参数调用能够以最优化的方式进行而去使用call traits。
2.comress pair是我不太推荐使用(为了一点点空间,而增加理解的难度不值,推荐的方式是将来STL的pair实现就是compress pair)
3.array是推荐使用

原则是,有利于抽象和源代码易读性的用,否则不用.............

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- blog.jtianling.com



阅读全文....

Google Music Beta试用及中文歌名/歌手名乱码研究

很久没有写博客了.........今天发现访问量已经超过80W了,很感叹,今天不谈技术,谈谈技术外的娱乐~~~~

在Google I/O刚放出Google Music Beta就赶快冒充美国IP去申请了邀请码,在前两周很幸运的收到了邀请被确认的消息,第一时间开始逐步的把听音乐的习惯迁移到Google Music Beta上。发现虽然Google号称自己在Google Music Beta上使用了Flash技术并且没有提供iOS版本的App,导致大部分人感叹Google故意以此来限制Google Music Beta在iOS设备上的使用,说实话,这是以小人之心度君子之腹了~~~~因为就我实际的使用发现,不仅iPad可以直接通过网页形式使用Google Music Beta,iPod也行(虽然屏幕实在太小,操作不方便),只是播放的时候点一次播放不行,需要暂停了再点播放,就能正常播放了。

Google Music Beta最大的特点就是在云端,而且全都是自己上传的音乐(虽然没有码率提升功能,但是事实上也算是可以完全控制),但是Google Music还是有一些缺陷:
其一:支持的音频格式实在有限,特别是不支持无损格式,比如ape。很多无损控估计会很郁闷。
其二:没有歌词,对于中国人,因为有英文歌的存在,估计很多人会像我一样希望有歌词,当然,即使不听英文歌,你听周杰伦歌的时候,歌词也是需要的~~~~~~

然后英文歌的播放使用一如既往的没有问题,但是中文歌的歌名/歌手名显示碰到一个很大的问题,有时候会是乱码,在网上查了查,发现很多人有类似的问题,但是却都是提出Google Music Beta不支持中文,没有任何解决方案,对此我较为不爽,所以尝试自己找找解决方案,不就是编码问题嘛,我感觉Google怎么说也得支持下某种形式的Unicode吧.

于是....尝试开始.....

Windows下:

QQ Music:

交互设计非常好,用户界面感觉很棒,下载方便,音乐库音乐丰富,音乐的码率较高,也有关于mp3 tag的设置,可以写入ID3v1 与/或 ID3v2,还可以设置ID3v2的编码为ISO-8859-1或者UTF16, ID3v1乱码,ID3v2 + ISO-8859-1乱码,尝试设置ID3v2 + UTF16后在公司的Windows7的机器上,Google Music Beta的网页版本中文显示正常,但是ipad上和Android上的Google Music中文不能正确显示.(此时怀疑Google Music Beta在Android上和iPad上使用的编码是utf-8?)另外,对于QQ Music我还有个不满,那就是专辑的tag信息经常是啥 "20xx年xx月新歌速递",不知道在腾讯的QQ Music的编辑是怎么想的......简直是为了推广逆天行事啊~~~~~~~或者是因为程序的原因?不通过修改弄成一个专辑,不能合适的通过一个专辑页面发布?总之这样的行为是大大破坏的其歌曲信息的正确性,完整性,非常不方便用户管理.但是最最奇特的是,回到家中,使用Windows XP的QQ Music,虽然同样的是设置为UTF16的编码,但是上传后显示竟然一切正常了,不知何解。

千千静听:
老牌的音乐播放器,原来本地听音乐的时候为了更好的歌词效果舍弃了Winamp然后选择了他.放弃千千静听的最大原因是它被百度收购了,然后在尝试向网络播放器转型的过程中太乱了,广告一堆一堆,看的我非常烦躁,一个播放器不老实的在后台呆着,你老给我蹦窗口,谁受得了啊,就像当年卸载搜狗输入法一下,碰到这种情况,毫不犹豫的卸掉,再也不想用.正好碰到1g1g和Google Music(cn版)慢慢就习惯了在网上听了.告别千千静听这么久了,这次因为Google Music Beta的问题,才特别的回来看看.发现千千静听老牌就是老牌,虽然下载不是很方便,音乐库的界面排的太挤,比较丑陋,本地播放器的现在的默认界面颜色也太惨白(甚至还不如以前我用的时候好看),但是关键是,从音乐库下载音乐回来后,发现在Google Music Beta中的乱码问题解决了,在Android和iPad上都能正确显示~~~~~老牌的东西果然考虑问题就和新加入的小弟小妹不一样,就是能解决问题........千千静听的Tag编辑一栏有个高级选项,可以转换内码.(这点也体现出了千千静听耕耘音乐软件多年的技术积累及软件功能积累,当年我喜欢千千静听的原因之一就是编辑MP3的信息非常方便,甚至有自动从文件名识别等批量编辑方式)首先我看了一下能够正确显示中文的音乐文件的内码,竟然显示是GBK,这个我很惊讶......

Mac OS X下:
沒有特別好用的本地网络播放器程序,所以一般用网页版本的替代品.

Google Music (cn版本)
因为全是正版,刚出来的时候很喜欢用,可以较为方便的下载回来,而且tag信息很全,只是后来好像一直没有啥新功能加入,在Windows下一般就用QQ Music了,只是在Mac下才使用,这次使用发现果然是Google自家的东西,下载回来不仅信息挺全,上传到Google Music Beta后,在Android和iPad上看也一切正常~~~~看来Mac下该用什么没啥好试的了,因为也不用改.


最后,一些跑题的尝试:
尝试在QQ Music中下载一个音乐回来,(已经设定为UTF16的编码方式保存tag)然后在千千静听中编辑信息,高级选项中编码显示也是GBK后,再上传.发现显示正确......这点我非常无语,也就是说,因为QQ Music在Windows7上确实是以UTF16的这种Unicode方式保存Tag信息,在Google Music Beta中无法正确显示中文,而在WindowsXP上虽然选择的是UTF16,但是实际还是GBK编码,因为Google Music Beta接受显示中文的编码是GBK~~~~集体晕倒去吧.....看来用unicode生,不用unicode死这个原则碰到Google这种家伙不好用了.......加上Google Music (cn版本)下载回来的音乐文件也能正确显示的事实,是不是就是因为Google Music (cn版本)的tag用了GBK编码,所以Google Music Beta为了支持自家产品,所以做出这么奇怪的设计啊?

最后想到在Windows7下,假如不想改变使用QQ Music的习惯,又想保持Google Music不乱码,我发现也有方法了,那就是用QQ Music下载,然后用千千静听转码~~~~因为千千静听选项中有个自动监视文件夹改变的功能,所以相对来说,只要将其开在后台,就能自动的将QQ Music新下载的歌曲添加到播放列表,然后再设置Google Music Manager为每隔一段时间添加歌曲的方式(不然的话感觉在还在下载的时候就已经开始上传了),这种方案已经基本能解决问题了~~就是额外的多开了千千静听,感觉挺累赘,还要接受他的弹窗....习惯使用千千静听的童鞋那就爽了,继续用千千静听就好了~~~~当然,假如有天QQ Music能够解决此问题就更加完美了.

以上研究的最大问题在于关于Windows7部分乱码的原因和解决方案还有待回到公司检验,现在周末在家只有WindowsXP的机器。

阅读全文....

【转载】偶是程序员

不转不行,太有意思:

来自:http://blog.csdn.net/nnnnyyyy/archive/2011/05/10/6410554.aspx

 

最欣赏的是这个:

 

 

以下是原文:

 

偶是一个程序员。

偶的生活很简单.确切地,用两个词,来说,就像偶写的代码一样,单调且无聊。
早上七点被手机叫醒:

 

睁开眼,刷牙,洗脸,吃早饭

八点半到办公室是必须的,然后开始一天工作:

 

那什么时候下班昵?偶不知道是会是几点钟:

 

从日出到日落,从星期一到星期天,日子就像是写错了代码产生的一个死循环:

 

每周双休?对偶来说只能是一种奢望:

 

平时,他们都叫偶:X工。其实只有偶最清楚,X工不是说偶是一个姓X的工程师,而是一个姓X的
民工:

偶没有钱,

 

偶也没有MM,

 

有的只是改不完的虫虫(Bug)和发不完的版本,

 

2007年比以往时候来的更早一些的第一场雪下过之后,偶买了股票和彩票

 

但是最终发现还是回家做豆腐的好;

 

但是残酷的现实总是令人无奈的,穿偶的眼神你完全可以发现"无奈"这个字并不是那样的空洞 :

偶真的是:叔可忍,婶也不能忍(出处:赵本山小品。原文:是可忍孰不可忍):

 

面对这样的生活,偶要大声吼一声:

 

遥想当年偶是一个:

 

曾经是那样的意气风发:

 

在夜深人静的时候,偶弹起心爱的土琵琶,唱起那动人的歌谣(柯受良-《大哥》):
偶写了代码好多年,
偶不爱冰冷的床沿,
不要逼偶想念,
不要逼偶流泪,
偶会翻脸

 

偶问自己:难道偶的人生只能是一个杯具么?

 

不是,不是这样的.

偶坚信 
MM,会有的

 

面包也会有的

 

爱情... ... 也会有的

不要问为什么,就因为偶们是一个程序员。偶们勤劳、善良,上天会眷顾偶们的。

看到这篇帖子的和没有看到这篇帖子的都请大声地祝福偶们吧

阅读全文....

Google为啥没有Java的style guide(编码风格指导)

印象中,听说过Google内部使用最多的三种语言是C++,Java,Python,但是很奇怪的是,Google style guide,有C++,Python的,还有Javascript和objc的,估计内部用的也不会少,但是就是没有Java的,这个很让人纳闷,为什么呢?

在stackoverflow上,还真有人问起此问题:Why no Google java style guide?

么就没有呢?怎么就没有呢?Google怎么在Android中都选择了Java作为开发语言,就是偏偏Style guide没有Java呢?
有人的回答很二:You have to ask Google.
也有很多有意义的回答:
1.Google's Java style is pretty much the same as the standard Java style
Google的java style与Java标准的style(Sun的)非常像。所以不需要了,也许吧,不过Apple也有objc style guide啊,为啥Google有objc的呢?因为Google看Sun更顺眼还是看Apple不顺眼?这个我就不知道了。
2.Joshua Bloch - Effective Java 2nd Edition is Googles style guide
Effective Java就是Google的编码风格指导......这个太牛了。提供一个第一版的pdf版本给大家看看吧,有钱请买正版的第二版。

还有几个真正出自Google的JAVA相关编码风格指导可以作为参考:
Google在Android开发中推荐的编码风格,因为该网页在google sites上面,因为众所周知的原因,比较难以访问,我拷贝了个evernote版本
GWT的Code style文档

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

OpenGL(ES) 线性插值算法黑边问题探源

第二次使用别人的引擎碰到用OpenGL线性过滤算法放大图片出现黑边的问题了,而引擎的制作者竟然不知道怎么解决,两次碰到此问题时都是试图教导我使用最近点过滤方式绕行,我很无奈,帮助其解决一下,顺便将问题简单的记录于此。

OpenGL在放大图片时有两种方法,一种是最近点(NEAREST),一种是线性(LINEAR),虽然在OpenGL里面,设置纹理参数的时候都称为过滤(filter),都通过glTexParameteri函数设置。比如二维时,设置线性过滤:

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

放大时实际算法为插值(
interpolation)。

具体的
最近点过滤算法参考


线性过滤算法参考

简单的讲,最近点过滤算法就是用最靠近像素中心的那个纹理单元进行放大和缩小,效率更高,效果不好,锯齿严重。

线性过滤算法是对靠近像素中心的2*2纹理单元(二维时,三维为2*2*2),取加权平均值,用于放大和缩小。效果更好,效率稍低。(参看《OpenGL编程指南》第六版)

一般来说,我们常用Linear方式,但是Linear方式有个问题,那就是碰到边缘时怎么处理的问题,一种是取边缘外元素作为普通点进行加权计算,一种是不取。

为了方便演示,我使用一张Android SDK中附带的图片,并放大2.0f倍,多次紧密排列绘制,以观察效果,主要绘制源代码如下:

void DrawImage(float x, float y, float scale) {

 glBegin(GL_QUADS);

 glTexCoord2f(0.0  , 0.0  ); glVertex3f(x, y, 0.0f);

 glTexCoord2f(1.0  , 0.0  ); glVertex3f(x + (gImg.Width * scale), y, 0.0f);

 glTexCoord2f(1.0  , 1.0  ); glVertex3f(x + (gImg.Width * scale), y + (gImg.Height * scale), 0.0f);

 glTexCoord2f(0.0  , 1.0  ); glVertex3f(x, y + (gImg.Height * scale), 0.0f);

 glEnd();

}

void DrawImages(float x, float y) {

 DrawImage(x, y, 2.0f);

 DrawImage(x + gImg.Width * 2.0f, y, 2.0f);

 DrawImage(x, y + gImg.Height * 2.0f, 2.0f);

 DrawImage(x + gImg.Width * 2.0f, y + gImg.Height * 2.0f, 2.0f);

}

当然,这里我主要关心linear方式,所以:

 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,

                 GL_LINEAR );

 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,

                 GL_LINEAR );

在默认时,OpenGL
是默认设置GL_REPEAT的

,此时,加权的纹理单元是从原纹理单元的相反一侧去取。效果的好坏依赖与图片的内容。

绘制4张图片时感觉效果还行:

但是仅绘制上面两张图片时,效果明显有问题,可以看到下面有明显的白边(加权计算来自于上面白色的状态栏)

在OpenGL中,还有几种情况

1.GL_CLAMP,线性算法会取边框外的像素点进行计算,导致黑边,这也就是常见的黑边效果。

2.
GL_CLAMP_TO_EDGE,忽略边框,为简单设置时想要的正确效果。

3.GL_CLAMP_TO_BORDER,添加边框颜色值,在纹理坐标超出边框时,按设定的颜色值进行计算,在没有为边框设置值时,效果类似GL_CLAMP。(可以将此时的边框值看做为黑色)

比如,我用如下方法,设置一个红色边框值,  

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);

float color[4] = { 1.0f, 0.0f, 0.0f, 1.0f };

glTexParameterfv( GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color);

效果就会如下,明显多出一个红色边框:

加入还觉得不够明显的话,修改draw函数,

void DrawImage(float x, float y, float scale) {

 glBegin(GL_QUADS);

 glTexCoord2f(-0.1  , -0.1  ); glVertex3f(x, y, 0.0f);

 glTexCoord2f(1.1  , -0.1  ); glVertex3f(x + (gImg.Width * scale), y, 0.0f);

 glTexCoord2f(1.1  , 1.1  ); glVertex3f(x + (gImg.Width * scale), y + (gImg.Height * scale), 0.0f);

 glTexCoord2f(-0.1  , 1.1  ); glVertex3f(x, y + (gImg.Height * scale), 0.0f);

 glEnd();

}

这下意思明显了吧:

以上是OpenGL的情况,OpenGL ES的情况又需要单独讲一下:

OpenGL ES 1.1中,只有两种情况,REPEAT(默认),和
GL_CLAMP_TO_EDGE。
参考这里

Android的情况,在我手机(Nexus S)中,默认的Repeat方式,会看到黑边。(这个有点奇怪,与OpenGL中的现象不一样)设置为
GL_CLAMP_TO_EDGE后,问题解决。

iphone上的情况,望知情人通知,目前没有时间测试。


 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com


 

阅读全文....

一周Qt使用小结

 


     初创公司,没有像大公司那样的技术积累,很多东西需要自己从头干起,其中较大的一块就是工具。想起刚刚参加工作半年的那会儿,老板为了说服我写工具,说让我进工具组是培养我,写了一个数据校验工具后,我就死活再也不肯开发工具了,硬要继续写服务端程序。现在真是轮回,这次我是发现,我们实在是太需要工具了,于是决定自己写工具来提高公司的游戏开发效率。

     于是,首先用本周时间,开发了一个用于编辑游戏layout的工具,使用的UI库是Qt。这个虽然是在公司有在MacOS下使用工具的需求下做出的选择,但是还是不得不说,对比3年前学习和使用MFC的经历,总体来说是心情愉快了很多。

     Qt的学习断断续续,虽然我的博客中有个Qt的分类,但是在之前其实了解的还是比较浅的,除了大概翻看过《C++ GUI Qt 4编程》(第二版)一书,用Qt结合OpenGL做了一些小的动画demo,基本没有开发过啥实际的东西。说起来这还是第一次在工作中使用到Qt,中间多少还是走了一些弯路,也碰到过一些问题,这里大概总结一下,想到哪就说到哪了。

  • 很难避免要说的是与MFC的比较,但是我前面说了,我们公司用MacOS的人较多,跨平台是硬指标,MFC无法选择。至于其他类似的跨平台库wx等的选择,见以前的文章。就中国目前的国情,已经自己较低的水平,为了避免口水站,这部分就省略了。
  • 就使用体验来说,Qt的确比MFC的设计好了很多,因为Qt原来就是个靠卖库生存的公司,库和API的设计就是公司的核心竞争力,要是设计的不好,公司就没法生存了。所以感觉API的设计上的确是花了很多功夫的。(参考这个文章)signal/slot系统的存在的确让设计灵活了很多,开发的过程中甚至都能让思维更加一致和清楚,因为你在设计和使用某个类时,完全可以暂时不考虑其他类。(这是理想情况)设计时假如有模块化的思想,signal/slot可以很方便的用于模块间的解耦。
  • Qt更加完全的面向对象,这点很多人都提到过,不过其实,我感觉有的时候这个面向对象甚至有些稍微过头。
  • 随着学习的语言和各类框架,库越来越多,越来越杂,慢慢感觉到其实光是学习某个东西都是了解的非常浅,只有切实的使用这个东西开发达到一定规模的工程时,才能慢慢的有所领悟,然后才能进一步的掌握。其实断断续续学Qt很久了,但是直到这一周(即使仅仅是一周时间,2千行多一点的代码规模)的密集开发,才感觉慢慢了解了一些东西。
  • Qt的designer工具:基于一贯的习惯,不是太喜欢类似的工具(从MFC时代来的经验),而是愿意手工编码,首先是感觉手工编码时自己对代码更加了解,防止生成一对垃圾代码,不好懂,也难修改。其次是感觉手工编码比用鼠标去拖拽效率还能更高一些-_-!(这个是纯个人意见...........)对于大量重复的操作,要么抽象一下,要么借助神器vi........都不是啥问题。
  • 帮助文档:MSDN是我见过最牛的,不过Qt的assist也不差了。
  • Model/View的引入使得Table和list控件使用更加的方便,这个有人不同意,甚至形容其为“脱裤子放屁....”,我觉得是还没有理解到底该怎么使用。我刚开始的时候,使用Qt预定义的QStandardItemModel与QTreeView的组合,发现的确相当费劲,好端端的平白无故加入了Model的操作,还要关心Model与View之间的交互,而自己的真实数据又感觉直接保存在QStandardItemModel的对象中使用非常不方便,还是需要额外再保存一份,然后每次来回同步着调用,此时真是感觉Qt的Model/View设计的真废。(对,当时我就是感觉Qt本身设计的废,本身这种MVC的变种模式使用应该是要更方便才对)后来第二次开发另一个东西的时候,直接用QTableWidget,相当于是自己的数据+Widget的组合,用着是比较自然了,但是感觉还不是足够的方便。然后查阅了一下资料,对其进行了重构,通过自己定义的Model来实现Qt中的Model/View,此时才感觉到使用的方便,这种使用方式,就和iPhone中的委托很像了,数据可以仅仅保留自己的一份,然后通过使用这些数据来实现自定义Model中的接口,任何时候我的修改都是直接针对于我自己的数据的,完全不用关心view层的事情,(最多是通知刷新)不仅仅是只保留了一份数据,而且在操作上带来的极大的方便。
  • Qt的XML接口实在算不上是方便的,假如排除有些类可以直接作为某些XML接口的参数的因素,很多其他的xml库使用都要更加方便。(比如tinyxml和rapidxml)当然,其实对于简单数据,个人更喜欢json。
  • 一方面是为了学习,另一方面是为了减少类型转换,我大量的使用了Qt的容器和String类,感觉与std的设计大同小异,但是算法库稍微弱一点。
  • 绘图时QImage+QPainter的组合非常方便,虽然我是做一个游戏的layout编辑工具,但是完全不需要使用到opengl。(目前没有考虑到半透明的情况)
  • QDockWidget控件在Windows下的效果非常棒,但是MacOS下效果一般,不够美观,边框的拖动响应也不是太好,特别是调节大小的鼠标提示很难出来。
  • 有意思的现象是Windows下的字体比Mac下略小,所以假如是固定坐标的对话框(没有用layout),最好是在Mac下设计,不然的话有可能在Mac下显示不全。
  • Qt程序的发布,Windows下编译Release版本后,拷贝需要的Qt Dll即可,Mac下对编译好的app使用macdeployqt命令(甚至可以通过参数-dmg 打成dmg包)
  • 自定义Model的data()函数,当role == Qt::EditRole时,显示的是此格在被编辑时的内容。《C++ GUI Qt4编程》一书未描述,文档中也没有详细描述,因为刚开始编辑时总是会出现一点击编辑总是空的情况,自己猜的,不过还真正确。



使用Qt的时候,还走了一些弯路,一部分也算是自己了解Qt不深入,一部分应该也算是Qt的设计问题。
Model的自定义使用:
insertRows,removeRows需要自己实现,大部分时候仅仅需要:

  beginInsertRows(parent, row + 1, row + count);

  endInsertRows();

  return true;

  beginRemoveRows(parent, row , row + count - 1);

  endRemoveRows();

  return true;

但是还是需要自己实现,不然的话实现是空的,那么是没有删除和添加效果的。相当不理解,那rowCount是干啥用的?其实应该只需要update/refresh一下就好了。或者,emit一下Qt中已经有的rowsInserted或者rowsRemoved signal也就好了,但是在rowsInserted,rowsRemoved信号的文档中明确的表示这两个消息不允许子类调用的,“It can only be emitted by the QAbstractItemModel implementation, and cannot be explicitly emitted in subclass code.”

而insertRow和removeRow是调用insertRows和removeRows来实现的,(文档如此描述)所以我们不需要实现了。不知道哪种逻辑更为正确,插入多行是多次插入呢,(所以插入多行可以通过多次调用插入一行实现)还是插入一行是插入多行的特殊情况呢?(就如同Qt这样反过来实现)
beginMoveRows和endMoveRows系列就更有意思了,因为没有moveRows用于重载..............那么,这些protected的函数什么时候调用呢?

最后找到了layoutChanged信号,发现只需要在改动后emit此信号即可刷新。并且insert和remove都可以实现。原来........Qt设计者眼中的update/refresh名字叫做layoutChanged.........相当晕。

使用QAction作为快捷键的时候,在一个列表空间中创建,发现无论如何都无法出发triggered信号,最后只能在全应用程序的菜单中添加action了事........这个比较困惑,也就是说没有局部快捷键?
对Qt的了解有限,使用一周,为了防止同一个坑掉进去两次,特写下一些东西作为回头查阅的资料,觉得不对的请提出来。

 

阅读全文....

网络协议编写的三层境界

本文不会涉及到你该使用UDP还是TCP,是FTP还是HTTP,也就是跟P都没有关系。^^ 同时,也不涉及该使用私有协议还是标准协议,还是在标准协议下使用私有协议, 而是会谈及在具体的协议编写的时候,使用哪种编写的方式会更好。由于本人的知识有限,谈及利弊时,主要以使用C++编写服务端时的经验为主,至于同时适不适用于其他语言,就靠童鞋们自己分辨了。

 

第一层境界:新手入门

    因为我一开始工作的第一家公司就是精于服务端程序编写的公司(运营过百万级同时在线以上),所以实际上我以前都不太清楚真有公司处于此水平,直到真的碰到时,我才惊慌失措,感叹不已。

特点:信手拈来,直观质朴

也就是写一个结构,在任何需要使用的时候(典型环境就是打包解包,读写文件)一个变量一个变量的通过memcpy等方式处理。简单是简单,没有任何抽象。
但是,

1.非常的不符合DRY原则,在此情况下,服务器端的打包解包代码和客户端的打包解包代码都得两份。别说服务器只需要打包,客户端只需要解包,这是太理想的情况,太经常一个小结构会需要传来传去的。而无论这个结构用多少次,你都得多为其写一次代码。

2.扩展性差:任何底层协议的改动,你都得更改除了相关数据结构意外的地方,(这也算是不符合DRY原则带来的副作用)而且此更改你往往得通过搜索才能完成。甚至,同一个数据结构即使仅仅是打包解包都做不到DRY原则。需要进行版本控制时,相关代码也会散布在各打包解包各处,更何况,当你想要以其他方式(比如写入文件)保存此数据结构的时候,你又得重新来一次,简直就是不人道的。
3.容易出错:在上层打包解包代码,都需要关注于每个结构的每个字段的数据类型,任何一个类型错误,你能够预期到的最好结果就是crash。

第二层境界:序列化

    通过序列化的概念,通过函数抽象实现同一个数据结构打包解包的DRY。通过函数重载,减少不同类型的不同处理。

特点:统一接口,各司其职

    面向对象有的时候会代码对象层次过多等乱七八糟的问题,但是此处面向对象的使用,我感觉实在是太淋漓尽致的体现将面向对象的好处了。我记得以前有个关于对象设计的原则,那就是告诉对象要做什么,而不是去获取数据自己来做。在第一层境界中的做法就完全是自己获取结构中的数据,外部来完成工作,为什么不更面向对象一点,让这种工作由对象本身来完成呢?此时,因为序列化的本质是从接口到二进制之间的转换,对于网络打包解包,文件读写可以做到通过传入不同参数用同样的接口来完成,对于每个类/结构的数据只需要进行一次的编码,极大的减少了错误的发生概率。 能达到这个境界的工作已经算是比较有技术的公司了。
但是,
1.对于服务器客户端语言不同时的情况,再次的无法实现DRY,典型的应用就是以JAVA写服务器,而以C++写游戏时。
2.版本控制还是太过于手动化。

 

第三层境界: 代码生成代码

    《Unix编程艺术》中描述的至高境界....代码生成代码,元编程的本质。第一次领悟这种境界是通过Google Protobuf,后来还知道一个thrift

特点:描述结构,自动生成

    以Google Protobuf为例,在写一个网络协议的时候,你不是直接的用一种语言编写协议,而是用特定的描述语言来描述这个协议的内容,然后通过工具自动的生成你需要的特定语言的结构。这样的好处是一次的描述,可以自动的为你生成多个语言的协议文件。(DRY,Protobuf官方支持C++,JAVA,Python,第三方支持的更多)更重要的是,这个接口的打包解包接口已经也生成好了,直接调用即可。当然,这个方法也不是完美的,简单的说,你需要学习怎么描述这个协议,你需要用工具生成代码,简单的说就是比直接写增加了复杂性。

 

小结:

  其实还有其他的协议编写方式,比如用XML,Json的纯文本协议,这个也是一种较佳的方式,调试非常方便,只是效率上比起二进制的还有差距,而在第三层境界中,为了调试方便,为生成的结构增加一个日志输出接口,也能较为方便的调试。

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

新浪微博与腾讯微博的开放平台比较 -- 从程序员的角度

write by 九天雁翎(JTianLing) -- www.jtianling.com

新浪微博 -- 腾讯微博 -- 讨论新闻组 -- 代码库 -- 豆瓣

新浪微博

    新浪微博应该算是近期最火爆的了,虽然算是中国社交网络中较新的成员,但是却最具有王者的霸气。目前已经完全凌驾于传统社交网络人人网和开心网之上了,(甚至有数据显示此两者在萎缩了)这点对比国外的facebook和twitter,不禁感叹一下中国的特色国情。
    大概看了一下文档,整个API是基于http的XML和Json文本协议,不知道是发扬光大于facebook还是twitter开放平台,这种方式极大的提高了API的可用性,简化了客户端方面对API的使用方式。这也是《Unix编程艺术》一书中,Raymond评价很高的一种互联网协议设计。
很欣喜的是,新浪微博光是objc的SDK就给出了两个:
一个是官方的:http://code.google.com/p/weibo4objc/
一个是由网友@宝玉xp提供的:http://code.google.com/p/sinaweiboios/

最后发现事实没有想象的那么好,官方的那个感觉代码写的很稚嫩,API设计的感觉虽然简单,但是不足够好,可能作者并不是专业的objc开发者,就新浪的实际情况来看。。。。会不会是做微博的PHP开发者-_-!虽然作为SDK使用者,不应该这样说,毕竟吃果子不忘栽树人嘛,呵呵,但是还是自以为较为客观的描述一下,大家看看代码和API就知道了。

而网友提供的那个,根本就不是SDK,而是从TwitterFon客户端改造过来的一个应用程序,虽然感觉代码的成熟程度比前面的官方的SDK强太多了。
那么还是用真的SDK吧。注册帐号,添加新应用。

 

    再checkout了代码,更改帐户和密码和添加新应用时sina给的custom key,测试了一下demo.....发现报错,“limited application access api!”然后一查,(见这里)吓一跳,原来中国的开放平台就是这样的,碰巧被我碰到无通知的升级。哈哈,不是当机了就说升级吧。(谁开发过服务端程序谁知道^^)使用无果。
说到API设计不足够好,(代码就不说了,自己看吧)看一个最常用的例子:

Status * statusme = [weibo statusUpdate:@"api Test" inReplyToStatusId:nilReplyId latitude:nilLatitude longitude:nilLongitude];
weibo是Weibo类。我明明就想发一个纯文字的信息,结果需要拖这么长的一个尾巴。。。。。甚至还有带inReplyToStatusId的这种,为啥我发信息需要调用一个API来表示不是回复消息呢?完全可以,也实在应该再封装一层。statusUpdate: (NSString)就可以了。
而最让人意外的是其实是有下面这样一个带图片的新消息的接口的:
[weibo statusUpload:@"111" pic:@"pic_path" latitude:nilLatitude longitude:nilLongitude];
其实我刚开始以为这个API才是发布新消息的时候用的,但是我不需要图片的时候,尝试以下两种方式调用:

[weibo statusUpload:@"111" pic:nil latitude:nilLatitude longitude:nilLongitude];
[weibo statusUpload:@"111" pic:@"" latitude:nilLatitude longitude:nilLongitude];
结果都是会报错的。(本地的错误)这样的抽象,这样的接口,其实还是有点让人意外+无语的。

 

腾讯微博

    腾讯微博虽然发布的晚,(策略失误所致)但是因为其庞大的平台效应,发力后还是不容小觑。因为开发的晚,所以目前开放平台也没有sina那么好,前1,2个月,我看其开放的SDK时,甚至都还没有iphone平台下可用的SDK。但是好在现在有了。

稍微浏览了一下代码和API,发现腾讯iOS SDK中的代码比新浪的要干净漂亮很多,呵呵,可能开发的人要专业一些(虽然以前出过抄袭事件,但是感觉开发者还是要专业一些)不过专业是专业了,API设计的也实在是太专业了:

QWeiboRequest *request = [[QWeiboRequest alloc] init];
NSURLConnection *connection = [request asyncRequestWithUrl:url httpMethod:@"POST" oauthKey:oauthKey parameters:parameters files:files delegate:aDelegate];

类似的API从表面上是做到了一条API干净简洁的通过参数(上面的parameters)完成所有任务,而且还有增加新功能不用添加API的优点。(仅添加参数即可)但是实际上将所有用户拉入只能先看文档后写代码的境地,也许设计API的人应该先看看《设计Qt风格的API》,了解一些API设计的基本原理吧。API是设计给人用的,而不是想着自己怎么设计简单来设计的。对于这种API,我个人是觉得比新浪的那种还要糟糕。

稍微了解了怎么使用后,尝试添加应用,我一下就蒙了,需要填写包括手机号码,身份证号码,地址,公司等一大堆的东西,怎么搞的像审查罪犯?

    再结合这个权限的设置,真是受不了啊。有人来做开发者,对平台是有好处,最后好像是开发者求你?我联想到最近的“You Win, Rim”的事件,套用其原话,此时此刻,千言万语汇成一句话:你赢了,腾讯。做开放平台难道是来给开发人员添堵的吗?姿态开放而心态不开放的开放很明显是假的开放。最后的效果也很明显,目前新浪微博平台上的应用很繁荣,而腾讯微博平台的应用几乎没有,这可不全都是因为慢了一步而已。

小结:,,

    假如不允许批评,那么所有的赞美都没有意义。我较为苛刻,对两个微博的SDK都提出了一些看法。我的感觉,新浪需要的是加强技术,腾讯需要的是改变心态。但是无论是新浪还是腾讯,只要能够真的拥抱开放,我想不仅是两个公司的平台能够做的越大越强,同时也能给个人和小团队提供新的机会,让大家在中国这片神奇的国土上,也能做点符合自己理想的事情,同时,获得一定的收益,那样,也算是中国互联网在不停的倒退的时候的一点进步吧。

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....