显示标签为“电子工程师乱弹编程”的博文。显示所有博文
显示标签为“电子工程师乱弹编程”的博文。显示所有博文

2014年6月25日星期三

重新写一个精简的核心库

最近的带的项目陷入了软件开发的危机,原因是陷入自家写的库造成的泥沼越来越深。

最近几年带的大的软件项目都是使用自家写的库,工具库utils,开发库commDev,核心库core,api库,可视化库viz等等。当然了使用自家的库开发软件有时候非常有效率,但是自家开发库的升级以及维护绝对是一个大问题,维护超过5万行程序本身就是一个大问题,而且这几年来因为项目压力,常常直接将很多东西放入核心库core里,导致核心库臃肿不堪,而且关于库的使用的实例程序以及接口或者api更本做不到时常更新,有很多文档已经四五年了,更本无法使用,所以导致最最大的痛苦是教新人使用我们的库。更为恐怖的是核心库对其他外部库的依赖越来越多,从OpenCV,Boost到现在的至少10个以上的第三方库,但是核心库更本也没有根据外部库进行更新,至今核心库还在使用OpenCV2.0。更为痛苦的是自家写的库还是使用VS2008进行编译,因为实在太大,以至于没有人有勇气进行修整,并使用最新的编译器进行编译。


现在实在受不了了,我终于决定重写核心库,并且放弃工具库utils,开发库commDev,api库,可视化库viz。新的核心库只有两个依赖OpenCV和Qt这两个第三方库的巨无霸。放弃的工具库utils,开发库commDev,api库,可视化库viz,全部使用Qt代替。新的核心库就叫CoreLite,只依赖于最新的Qt5.3和OpenCV2.49,保持绝对的精简,而且可以根据Qt和OpenCV的升级进行编译,而且使用VS2012进行开发和编译,希望能做到根据OpenCV更新的频率进入版本更新。

  • 新的库CoreLite的第一个tag就是CoreLite290,使用和OpenCV一样的文档结构。
    \build\include
    \build\x86 (\build\x86\vc11\bin, \build\x86\vc11\lib)
    \sources\modules\
  • 新的库使用Qt的QThread。
  • 控制Class的总数,只包括Qt或OpenCV无法提供的一些特性,例如Producer-Comsumer-Data Sink结构。
  • 带几个实例。
  • 保证库的可读性,使用Qt的api的写作标准。
  • 最大程度保证库的可维护性,其实就是要保持精简。

目前想到的就是这些了,以后在重新的过程中,如果还有什么想法,我希望能写下来记录一下,期望可以用一个星期将新的核心库改写完成。

2013年11月21日星期四

项目经验:扬长避短,使用外包

最近真正开始完全接手并开始管理两个项目,多带了两个员工,积累了一些经验,最近的心得是:一定要注意扬长避短,遇到自己的短处时候可以使用外包。

譬如,我带的项目组的优势是复杂工程能力,软硬件整合能力,而复杂图形算法的设计能力是我们的弱点,以前我总是想要自己解决所有的事情,在复杂图形算法的设计上浪费了很多时间,花了大量的精力进行研究学习,测试,正因为我们不擅长,事倍功半。现在我将这块外包,集中精力维护我们的优点,提高优势,努力保持我们的不可替代性,这个月来我们得到了很多的收获和提高。其实,劳动和报酬不是成正比,而是和劳动的不可替代性成正比。

2013年1月21日星期一

再议“程序员每天只需编程4小时”(一年半实践)

我在一年半前写过一篇文章“程序员每天只需编程4小时”,经过一年半的实践,我是否做到了这点呢?其实我每天编程还不到4个小时,但是做出了更多的“可用,有用”东西。注意我这里说的是“可用,有用”,我的代码质量也得到了显著的提高。

如何在编程技巧上提高自己的效率呢,方法还是老三篇:

1. Keep It Simple, Stupid!
老调长谈,但是最为重要的的坚持并真正地做到:
http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

2. 不要重复自己
今天读到一篇好文,里面的一段话就很好的展示了这个观点。
“If you write something once, you should make it a method.  If you write it twice, you have to make it a method.  If you write something three times, you should stop programming!”  “如果你写出一些代码,你应该把它做成一个方法。如果你写了它两次,你应该把它做成一个方法。如果你写了它三次,那你就别去编程了!”

3. 写最少的代码
如何写出最少的代码,最为重要的一点就是要多思考。很多程序员总是有一种迷思,一年写几万行代码的人就是很强。错,我们所要做的其实尽量让软件的代码最少。代码越少越好,bug就更少,就越不需要重构。写最少的代码其实也是为了实现优雅的代码的前提条件。
其实这半年来我写的代码越来越少,但是进行了更多的重构,删除很多“过时的老”的代码,写了很多更新更少的代码。目标就是一个,写出整洁的代码。最近一直在重构以前的一个同事留下的代码,这些代码真是毫无美感可言,我这种具有强迫症的人,在重构之前,做的第一件事是将是代码对齐,增加换行,改名,先是得到“界面上的整洁的代码”,然后才是开始真正的重构,以得到“逻辑上的整洁的“少的”代码”。

2012年11月18日星期日

德国工程师分布

今天看到一幅图,关于德国工程师分布,其中颜色越深代表那里工作的工程师越多。从这幅图可以读到很多信息,对于工业立国的德国而言,其实某个地方工程师越多,那个地方的工业就越发达,相对而言经济轻快就越好。延伸一下,这个图和当地的失业率还有房价也是正相关的,在德国,工业发达地方一般而言都有拥有工科的综合大学或理工大学。

Ingenieurdichte


图的原始资料来自2009年6月德国劳工局,由VDI Ingenieurkarriere制作。

2011年11月14日星期一

我们的XX项目使用的系统架构

上上个星期五,我们的大头和我们三个核心开发人员开了一个下午的会。决定11月24日,参加一个展览会,推荐我们的产品,也是算是一次了解我们的产品的市场反应的机会。但是这个产品的具体介绍(其实是一个专业软件,加上辅助的硬件系统),我将在正式上市的时候进行详细的说明,可能会在明年的2月份,这个项目的软件部分这里我就暂且称之为XX项目。

这个XX项目已经进行了一年,我从开始就参与开发,作为软件核心开发人员,看到即将上市,感到特别的兴奋,而且有些成就感,特别是这个软件的用户界面还是我一点一滴设计的,和很多程序员不同,我认为设计一个漂亮的GUI是非常有趣的事。

进入正题,今天谈谈这个XX项目系统架构。

开发这个软件我们采取的很多对我们来说比较新的开发方式和架构,我们采用模块化开发,模块设计,所有的模块都通过我们特别设计的类似Service Interface方式导出其数据与功能,所有的模块的接口通过特定的方式进行开放。这些模块都有自己想对应的测试模块,对其进行自动化测试,使用的统一的架构。这些模块我们不仅使用在我这里所提到的XX项目,而且还使用到我参与开发的几个项目,有的是运行在服务器上,有的则是在单机上运行的。

我们使用的架构类似下面所示,XX_GUI模块是用户界面模块。XX_APP(应用程序)获取各种模块的“服务”,这里类似中间层。XX_GUI(用户界面)通过我们特殊定义的event和XX_APP(应用程序)进行通信,目前我们的XX_GUI(用户界面)是使用Qt进行编写的基于单机,但是XX_GUI(用户界面)可以简单的通过替换改变成为Web-based user interfaces(WUI),例如使用Java,AJAX进行开发,如果使用Web-based user interfaces(WUI),并通过各种Web服务协议进行数据和事件通信,XX_APP(应用程序)进行简单的改造就可以放置在服务器上。


XX_GUI
  ^
  | 通过event进行通信 
  |   
XX_APP (应用程序)
  ^
  | 通过统一模块注册机制进行调用
  |
各种模块

 

XX_WEB_GUI
  ^
  | 通过网络进行通信 
  |   
XX_APP (应用程序)
  ^
  | 通过统一模块注册机制进行调用
  |
各种模块

 

进一步延伸,因为各种模块组合产生的XX_APP(应用程序)可以在服务器上运行,我们的XX_APP(应用程序)适用于类似很多大型平台采用的Service-Oriented Architecture(例如亚马逊),可将XX_APP(应用程序)功能作为服务发送给用户。通过统一Service注册机制进行调用多个不同的XX_APP(应用程序),组成一个复杂的网络服务。例如下面所示。这就因为使用同一Service注册机制这就大大提高服务的复用性。这就是在应用程序层级上提高复用性。

                         特定的服务
  ^                                                                        ^
  | 通过统一Service注册机制进行调用 |
  |                                                                          |  
XX_APP_1 (应用程序)    ... ...       XX_APP_n (应用程序)
  ^                                                                        ^
  |  通过统一模块注册机制进行调用         |
  |                                                                          |
各种模块                                           各种模块

 

最后总结一下,复用性一直是我对设计上思考的要点,简单来说:
我们的模块依赖于我们开发的类库,这就是在类层级上的复用
我们的应用程序依赖于我们开发各种模块,这就是在模块的层级上的复用
我们的服务依赖于我们开发各种应用程序,这就是在应用程序的层级上的复用


接下来的时间,为了准备这个展览会,除了设计一个产品演示,准备展位的广告,就是要好好再练一练德语了,突然发现自己的那点德语水平做一个好的产品演示推介非常吃力。

2011年9月11日星期日

不要让人重构你的烂程序

上个星期主要工作是重构一个很重要的模块,先抱怨一下,这个模块没有注释,可以运行,完全没有可维护性,写得是相当的丑陋。

单是理解这个程序就花了我和一个同事整整1天,然后重构花了我4天。而且每天我为了这个模块的重构,居然打破了我的“程序员每天只需编程4个小时”的教条,我每天竟然要花6个小时在这个模块的重构和测试上,最终我在重构上大概花了30个小时,所以很多人说不要轻易重构代码,这里我想说的是重构他人的烂代码还不如自己重写。
但是经过上个星期的经历,这里再次总结一下什么是烂代码。
1. 没有关于重要代码的解释
2. 没有统一的名称的规范
3. 到处都是注释掉的代码(注释掉的代码会让读代码的人很痛苦,而且会让人觉得这个代码不是最终的成品,而是实验性的代码,或半成品)
4. 巨大的方法(我居然看到了一个超过1000行的方法)
5. 很在方法中很随意的加入代码
6. 过于愚蠢的API的设计 (如果要学习如何设计一个好的API请查看Qt API Design Principles

上面第4点提到的,对于这个超过1000行的方法,被我改成的8个小方法,每个都不超过50行,而且这些方法的都具有复用性(因为每个方法很小,代码的可重用性开始指数增长)。为什么会有这种巨型的方法呢,其实就是程序员很多时候很随意的加入代码所造成的,而且他们满足于写出“可用”的程序,而不满足于写出“优雅”的代码。

很多人都停步于烂代码,因为他们认为他们的烂代码可以用,很多程序员都会犯这样的错误。他们很理直气壮的对我说,看我的代码很好用,可用的代码不代表不是烂代码,其实这些代码都金玉其外,败絮其中。人们常说,程序员都是从写烂代码开始,然而也许写出所谓的“内聚性、松耦合、零重复、封装、可测试性、可读性以及单一职责”的“好代码”需要长时间的锻炼,但是最坚持最简单的代码规范却是每个程序员可以做到的,这就是避免烂代码的最好方法。

其实我们常常并不要求人们写出“优雅,精妙”的代码,我们的要求不高,程序员只需要坚持“简单性”,写出符合“代码规范”简单的代码即可。

2011年8月15日星期一

程序员每天只需编程4小时

很久没有更新“电子工程师乱弹编程”这个系列。今天继续谈谈时间管理的方法。
其实我一直认为,作为程序员每天有效编程时间大约4个小时就可以,我每天就是最多编程4个小时,不是因为我没有什么活,其实我的工作任务在很多人看来是“相当繁重”的。减少编程时间后,剩下的时间用来学习,研究,可以进行“时间投资"(下文会介绍)。


如何摆脱加班的痛苦,减少工作时间,以下是我的几个小方法:


1. 减少”所谓的无效工作时间”
例如工作时上网时间,因为干这些事的时候,我们精神体能处于死亡的状态。编程时不要看邮件,减少外部的干扰。


2. 放弃完美
根据我的经验很多过长的工作时间就是我们追求完美所造成的。正如《卓有成效的程序员》所说的,“始终牢记你到底要做什么,如果情况开始失控就及时抽身而出。“80 10 10”法则,80%的客户需求可以很快完成,下一个10%需要花很大的努力才能完成,而最后的10%却几乎不可能完成。”我们常常为了那20%的东西,花费了80%的心力,我们所要警醒的是20%的东西是否是核心功能,对于不是重点的功能我们应该说“不”,并及时抽身而出,取和舍是减少工作时间的要点。


3. 使用合理的开发流程,开发方式,高效的开发工具
对于软件开发方法,我个人并不喜欢敏捷,应该所很不喜欢,因为软件质量和生产率成正比,因为软件的质量越高,生产率越高。而不是编码的速度越快,生产率越高。
我喜欢的开发方式是模块化开发,并且是采用相对传统的方式进行开发(常常使用迭代法,一定要画UML),我们组的系统的架构基本上是模块化的,每个提供对外接口,每个模块都要写一个对于的测试例程,一来是为了模块测试,二来是方便做持续集成的人理解。然后进行持续集成。这种开发方式相对比较累的人是做持续集成的人,我最近一直在做这件事。模块化开发出的模块,也可以提高代码质量,以及复用性。


4. 进行时间投资
这是我最近看《杠杆思考术》一书看到的一个概念。程序员的时间投资的根本就是找出“模式”。其实对于程序员而言,例如自动化构建的脚本就是一种时间投资方式,有时重做轮子也是时间投资的方式之一,例如自己写一些适合自己项目开发的工具类库,特别的工具类库常常可以在特定的项目开发中节约很多开发时间。以下摘抄一些书中原文:

因为投资而增加的时间,就应该用来投资打造新的模式或新的事业,进一步作为提升自己能力的自我投资,也就是所谓的再投资。如果能够反复运用这些时间,每年就可以产生数百个小时的时间资产,而且这些额外的时间还能够以复利的形式像滚雪球一样快速增加,所以投资效果就会越来越大。只要能够有效地缩短工作时间,就能争取到更多的额外时间,这样一来也就可以达到不错的成效,而且最终还可以跟升职加薪有所关联。
所谓的时间投资,其实是由几个基本步骤组成的。
首先,最重要的是事前的调查。环视一下自己的工作全貌,检查什么事情是很麻烦的,哪些地方很花时间,自己要完全掌握。
接着,就要检讨在这些工作中哪些项目可以进行的更有效率,也就是所谓的过滤。不管是跟成果有关联的事还是没有关联的事,只要是非做不可并且没办法交待别人的,都按照其重要程度排出先后顺序。而且,为了建立所谓的模式,就必须实际投资时间去实施,以追求效率化。在这种时候,如果无法达到预期的效果,就没有必要勉强坚持下去。相反,如果能够持续取得成果,就能累积时间资产。


当然大话谁都会说,找到适合自己的方法才是最好的方法,对了,今天你是否只编程4小时?

2010年6月22日星期二

不要“想当然”地编程

100_2891

本图和本文无关,摄于汉诺威动物园。

很久没有在“电子工程师乱弹编程”这个系列写一些东西,这个系列是我的一些关于编程“自爽文”的集合,今天有点时间,来写一个小小的编程体验。
今天下午又花了一大堆时间来找bug。
经过一番一番的跟踪才发现bug的程序代码。
找到bug后,又是一阵痛心疾首。

花了那么多的时间就是为了一个非常愚蠢的bug。
在代码中,我需要配长度为DEF_I_FRAME_BUFFER_SIZE字节的内存块。
DEF_I_FRAME_BUFFER_SIZE 长度为一个MPEG编码的I Frame的大小。
_last_buf = (uint8_t *)malloc(DEF_I_FRAME_BUFFER_SIZE);
我当时“想当然地”分配了200000。
自认为已经足够大了。

请看以下分析:
如果分辨率为500*500
使用了的是4:2:0 (YV12) --> 12 bit pro Pixel
8 Bit = 1 Byte
那么可以得到maximal frame size:
4:2:0 (YV12) --> 500*500*(12/8) = 375000 Byte = 0.375 MB
哈,就是这样,对于一个500*500一个帧也许会需要375000 Byte,就是这么简单。

“想当然”的足够大的size,就产生了一个愚蠢的bug。
使用malloc固然非常危险,但是最危险的是“想当然”的编程态度。
我常常不够谨慎地进行编程,一味求快。
这常常会出现很多思考的遗漏。
其实,在当时编这段愚蠢的代码的时候,自己问问自己,是这样吗?
如果自己的回答是不太确定,那么就去google一下,就像今天的例子,我当时只需要几分钟google一下,就可以分析出我所需要的buffer的大小。
然而,当时的“想当然”,就会产生后来的一个小时以上时间的浪费。

好了,最后再总结一下。

第一个经验为:
不要“想当然”地编程

第二个小小的经验为:
在动手找bug解决它的时候,一定一定要分析理解一下问题的本质。抓住问题的本质,根本,这很重要。
不要乱猜测bug的来源或者随意随机的修改,这肯定会让你的程序比找bug之前更糟糕。

2010年2月27日星期六

C++开发游戏手柄代码 (Developing the Joystick Code)

还是坚持每周一博,照例写正文前废话一段:最近天气越来越好,不下雪了,偶尔下点雨,下了一次冰雹,但可以看到10+的温度了,晚上6点坐车回家的时候天还是是微亮。哈,德国的春天终于来了。
迫于项目时间的压力,项目只剩下最后一周的时间,本周的工作效率还不错,写了非常多的代码,突然发现,我最近最能集中心力,最具生产力的时段是下午5到6点,就是回家前一个小时,似乎所有最有创造力的生产和想法都是在这个小时产生的,很奇怪。最近,所里的咖啡机又坏了,苦了很多人,有一个意大利同事对我说说,没有咖啡就不能Coffee in, Code out,没法编程了。还好我不喝咖啡,每天只喝茶,所里提供的各种奇怪的茶包(德国的茶包的种类真是多的恐怖,各种奇怪的口味都有,喝过一种咖喱口味的茶)。

好了闲话不多谈,今天谈谈关于游戏手柄joystick的代码开发,最近接触了一些多媒体子系统的Win32 API,其实非常容易,通过一些google可以找到非常多的源代码和示例代码,可以非常快的利用多媒体的Win32 API开发出一些很有趣的东西,游戏手柄joystick代码开发就是一个很经典的例子。支持游戏手柄现在是一个Win32的标准的一部分,多媒体子系统单独的Win32 API,其中支持游戏手柄是一个组成部分。但是关于游戏手柄的功能和数据结构没有定义在标准windows.h头文件,而是位于mmsystem.h中头文件。
上代码之前,一些说明:

[1]

要使用游戏手柄的Win32 API
#include <windows.h>
#include <mmsystem.h>

编译器请使用VS2005以上版本。

[2]

当然了不要忘记加入winmm.lib

[3]

JOYSTATE数据类型包括描述游戏手柄的State
例如

1: typedef WORD JOYSTATE; 
2: const JOYSTATE JOY_NONE = 0x0000L, 
3: JOY_LEFT = 0x0001L, 
4: JOY_RIGHT = 0x0002L, 
5: JOY_UP = 0x0004L, 
6: JOY_DOWN = 0x0008L,


[4]

我们要做的事就是,写出以下方法:

BOOL InitJoystick(); 
void CaptureJoystick(); 
void ReleaseJoystick(); 
void CheckJoystick();

 

[5]

CaptureJoystick()ReleaseJoystick()的方法就是使用使用两个Win32 functions就是:joySetCapture()joyReleaseCapture()
JOYINFO structure用于检索游戏手柄状态通过joyGetPos() function。

[6]

以下代码结合了Qt,也可以不需要Qt运行,只需要删除Qt部分的代码即可,使用了Qt的signal slot机制,利用了QTime的timeout()不断对CheckJoystick()进行调用,以及时更新Joystick状态。

[7]

BTP_Joystick以下的代码的主体部分来自网上的一些示例代码,我进行了一些修改和裁剪,编译和使用没有任何问题,我使用的是北通小手柄 BTP-C024 USB游戏手柄(如附图所示)进行测试,运行很好。以下代码非常简单,应该很容易理解,不要问我,为什么不能用,或者如何使用,如果不能用,请google或再看看代码(你已经有了完整的代码),呵呵。

 

 

 

 

/*
 * \file GameEngine.h
 * \class GameEngine
 * \date   2010
 * \bug
 * \warning
 *
 */

10 #ifndef GAMEENGINE_H_
11 #define GAMEENGINE_H_
12
13 #include <QtGui>
14
15 #include <windows.h>
16 #include <mmsystem.h>
17
18 //-----------------------------------------------------------------
19 // Joystick Flags
20 //-----------------------------------------------------------------
21 typedef WORD    JOYSTATE;
22 const JOYSTATE  JOY_NONE  = 0x0000L,
23                 JOY_LEFT  = 0x0001L,
24                 JOY_RIGHT = 0x0002L,
25                 JOY_UP    = 0x0004L,
26                 JOY_DOWN  = 0x0008L,
27                 JOY_FIRE1 = 0x0010L,
28                 JOY_FIRE2 = 0x0020L,
29                 JOY_FIRE3 = 0x0030L,
30                 JOY_FIRE4 = 0x0040L;
31
32 class GameEngine : public QObject
33 {
34     Q_OBJECT
35
36 public:
37
38     GameEngine();
39     ~GameEngine();
40
41     void HandleJoystick(JOYSTATE jsJoystickState);
42    
43     BOOL InitJoystick();
44     void CaptureJoystick();
45     void ReleaseJoystick();
46    
47     UINT m_uiJoystickID;
48     RECT m_rcJoystickTrip;
49     HWND m_hWindow;
50
51 protected:
52
53 private slots:
54     void CheckJoystick();
55
56 signals:
57     void joystickLeft();
58     void joystickRight();
59     void joystickUp();
60     void joystickDown();
61     void joystickButton1();
62     void joystickButton2();
63     void joystickButton3();
64     void joystickButton4();
65
66 private:
67     QTimer *_poller;
68
69 };
70
71 #endif /* GAMEENGINE_H_ */

 

1   /*
2    * \file GameEngine.cpp
3    * \class GameEngine
4    * \date   2010
5    * \bug
6    * \warning
7    *
8    */
9  
10  #include <QDebug>
11 
12  #include "GameEngine.h"
13 
14 
15  GameEngine::GameEngine()
16  {
17   
18      InitJoystick();
19      CaptureJoystick();
20 
21      //start timer to trigger every 100 ms 
22      _poller = new QTimer(this);
23      connect(_poller, SIGNAL(timeout()), this, SLOT(CheckJoystick()));
24      _poller->start(100); 
25  }
26 
27  GameEngine::~GameEngine()
28  {
29 
30  }
31 
32  BOOL GameEngine::InitJoystick()
33  {
34      // Make sure joystick driver is present
35      UINT uiNumJoysticks;
36      if ((uiNumJoysticks = joyGetNumDevs()) == 0)
37          return FALSE;
38 
39      // Make sure the joystick is attached
40      JOYINFO jiInfo;
41      if (joyGetPos(JOYSTICKID1, &jiInfo) != JOYERR_UNPLUGGED)
42          m_uiJoystickID = JOYSTICKID1;
43      else
44          return FALSE;
45     
46      // Calculate the trip values
47      JOYCAPS jcCaps;
48      joyGetDevCaps(m_uiJoystickID, &jcCaps, sizeof(JOYCAPS));
49      DWORD dwXCenter = ((DWORD)jcCaps.wXmin + jcCaps.wXmax) / 2;
50      DWORD dwYCenter = ((DWORD)jcCaps.wYmin + jcCaps.wYmax) / 2;
51      m_rcJoystickTrip.left = (jcCaps.wXmin + (WORD)dwXCenter) / 2;
52      m_rcJoystickTrip.right = (jcCaps.wXmax + (WORD)dwXCenter) / 2;
53      m_rcJoystickTrip.top = (jcCaps.wYmin + (WORD)dwYCenter) / 2;
54      m_rcJoystickTrip.bottom = (jcCaps.wYmax + (WORD)dwYCenter) / 2;
55      return TRUE;
56  }
57 
58  void GameEngine::CaptureJoystick()
59  {
60      // Capture the joystick
61      if (m_uiJoystickID == JOYSTICKID1)
62      {
63         joySetCapture(m_hWindow, m_uiJoystickID, NULL, TRUE);
64          qDebug() << " Capture Joystick success!";
65      }
66  }
67 
68  void GameEngine::ReleaseJoystick()
69  {
70      // Release the joystick
71      if (m_uiJoystickID == JOYSTICKID1)
72      joyReleaseCapture(m_uiJoystickID);
73  }
74 
75  void GameEngine::CheckJoystick()
76  {
77  if (m_uiJoystickID == JOYSTICKID1)
78  {
79     JOYINFO jiInfo;
80     JOYSTATE jsJoystickState = 0;
81     if (joyGetPos(m_uiJoystickID, &jiInfo) == JOYERR_NOERROR)
82     {
83          // Check horizontal movement
84          if (jiInfo.wXpos < (WORD)m_rcJoystickTrip.left)
85              jsJoystickState |= JOY_LEFT;
86            else if (jiInfo.wXpos > (WORD)m_rcJoystickTrip.right)
87              jsJoystickState |= JOY_RIGHT;
88             // Check vertical movement
89             if (jiInfo.wYpos < (WORD)m_rcJoystickTrip.top)
90              jsJoystickState |= JOY_UP;
91             else if (jiInfo.wYpos > (WORD)m_rcJoystickTrip.bottom)
92              jsJoystickState |= JOY_DOWN;
93             // Check buttons
94             if(jiInfo.wButtons & JOY_BUTTON1)
95              jsJoystickState |= JOY_FIRE1;
96             if(jiInfo.wButtons & JOY_BUTTON2)
97              jsJoystickState |= JOY_FIRE2;
98          if(jiInfo.wButtons & JOY_BUTTON3)
99              jsJoystickState |= JOY_FIRE3;
100         if(jiInfo.wButtons & JOY_BUTTON4)
101             jsJoystickState |= JOY_FIRE4;
102     }
103
104     // Allow the game to handle the joystick
105     HandleJoystick(jsJoystickState);
106     }
107 }
108
109 void GameEngine::HandleJoystick(JOYSTATE jsJoystickState)
110 {
111     //这里加入你自己对jsJoystickState的处理,以下只是极简单的示范
112     //通过HandleJoystick得到JOYSTATE
113    
114     //qDebug() << " get information about Joystick!" ;
115     if (jsJoystickState == JOY_UP)
116     {
117         qDebug() << " get Joystick -- JOY_UP!" ;   
118     }
119     else if (jsJoystickState == JOY_DOWN)
120     {
121         qDebug() << " get Joystick -- JOY_DOWN!" ;
122     }
123     else if (jsJoystickState == JOY_LEFT)
124     {
125         qDebug() << " get Joystick -- JOY_LEFT!" ;
126     }
127     else if (jsJoystickState == JOY_RIGHT)
128     {
129         qDebug() << " get Joystick -- JOY_RIGHT!" ;
130     }
131     else if (jsJoystickState == JOY_FIRE1)
132     {
133         qDebug() << " get Joystick -- JOY_FIRE1!" ;
134     }
135     else if (jsJoystickState == JOY_FIRE2)
136     {
137         qDebug() << " get Joystick -- JOY_FIRE2!" ;
138     }
139     else if (jsJoystickState == JOY_FIRE3)
140     {
141         qDebug() << " get Joystick -- JOY_FIRE3!" ;
142     }
143     else if (jsJoystickState == JOY_FIRE4)
144     {
145         qDebug() << " get Joystick -- JOY_FIRE4!" ;
146     }
147 }
148

Enjoy!

2010年2月13日星期六

牛年最后一周碎碎念

虎年快乐 最近实在太冷了,天天下雪,附图是法国鳄鱼牌的一个平面广告,我觉得很应景,简单改造成贺卡,祝大家虎年快乐!!每到春节,就好想回国过年。当然了,在这里,我还是有一些小聚餐,譬如吃一些远不如国内丰盛的火锅,哈,聊补思乡之情。

 

关于工作,这个星期开始集中使用Googletest进行单元测试(Googletest架构不仅可以很好自动化执行,可以很方便地包含在持续开发构建中,更重要的是极度简单易用,我非常喜爱),新项目的“业务逻辑层”一部份采用了测试驱动开发。这个星期比较能够集中心力,大概写了>3000行的代码,这对于我来说已经是一个相当相当的高产的一周,个人相当满意进度。

 

本周还继续丢弃了一些旧模块(内部结构很混乱,复杂),重新设计并重写,其实很早以前就想重写,但是一直下不了决心,在代码的复杂度面前总是显得相当软弱,只有当代码几乎彻底“腐烂”,不能适应新系统,才不得不重写,这真是非常不好的习惯。一定要改掉这个恶习。这次重构采用了同事介绍的一个很简单的方法,我认为还不错,个人命名为“小任务驱动重构”。方法如下:先彻底地理解一下以前的代码,然后重新设计结构接口等等,再对整个重构写一个非常细致(一定要非常非常细致)的计划,然后得到一个长长的由一个个小任务组成的“计划书”,以极微小的“粒度”进行重写,每完成一个小任务,在计划中将其删除,这样做比较有成就感,重写的过程就像自己和自己竞赛,例如可以给自己定一些每天必须要完成小任务的数量,并时刻注意代码风格,变量命名和错误处理,如果觉得编码枯燥时,可以进行一些文档编写。

 

我最近写的机器人控制程序,只剩下一些系统同步模块还需完善,完成后就可以进行和机器人的联机测试了,很是兴奋,因为可以使用所里最新购买的迷你机器人Khepera 3进行实验,对这个新的“大玩具”很是期待。

2010年2月7日星期日

本周编程乱弹及《编程匠艺》书评

snow_upb_2010 附图是家附近的雪景,大概一个月雪都没化,德国今年真是非常非常冷,雪也下得相当疯狂,地球变暖?还是真的似乎进入了“小冰河时期”。这个星期当然还是继续坚持“每周五天,信息斋戒”。

这星期接触了ICE,相当不错并很有趣(简介:“Internet Communications Engine”,是一个中间件平台。作为一个高性能的互联网通信平台,ICE包含了很多分层的服务和插件,并且简单、高效和强大。与硬件架构无关,编程语言无关,完全线程化,采用TCP、IP 和UDP作为传输协议,客户端和服务器代码都不需要了解底层的传输机制。),并实现了一个应用。还在做一个重构,进行中,采用了一个方法,就是对这个重构任务进行分解,把他们分成非常小的工作单元。并使用了原型设计过程,创建一些可丢弃的原型。个人认为原型设计过程在一定程度上可以说是非常高效和稳定。最近还改了一些编码风格,以便更为适应我们内部的编码风格。

这个星期也和一些bug战斗很久,因为一些指针的输入错误或糟糕的指针算法在以前的代码中,产生了很多的段错误(简介:也称为“保护缺陷”。源自程序访问那些并没有分配给它们的存储单元,涉及,都非常容易造成这种错误。说白了就是“访问了不可访问的内存”)。个人经验教训就是: 千万不能盲目,一定要清楚这些代码如何运行,编写代码一定要谨慎,并做到更谨慎做更多“防御性编程”,这样才能更好地防止bug出现。

但是这个星期最大的经验教训,就是明白了一个道理:“程序的记录分类很重要,一定要要好好总结整理”。以前编过一个测试服务器网络的程序,居然忘记曾经编过,这个星期四又编了一遍,而且还不会编了:-( 极度痛苦,最后,突然发现以前的代码,当时真是泪流满面。这个星期,师兄对我上传的代码进行了代码审查据说这是提高自己的最佳方式。这也的确提高了我对代码的责任感,强迫自己提高代码质量,修正自己的代码更加易于理解,消除所有重复代码。

最近进行了很多思考,项目压力下,反而进行了更多的思考,总结。常常在想如何能够“创作出组织良好而且易于理解的代码”,并阅读了《编程匠艺》,挺值得推荐的。这里将我在豆瓣的书评附上。

 

据说,传说中理想的程序员应该具有以下品质:
政治家。必须很老练,去应付那些怪异代码猴子的小过失,能够协调人员。
亲切。可以愉快的和别人合作。
艺术感。可以设计出优雅的解决方案。
技术天才。编写的代码可靠耐用。
也许我们还远未达到这种地步。但是从这本书中可以体会并学习:理想的程序员的“一些实践经验,一些思考方式,一些正确的心态”。
我很喜欢阅读这种类型的书,读起来非常轻松,不是什么纯技术类的图书,但是从中又可以获得很多系统化的知识。这本书共有24章,很厚,但是每个章节间是“松耦合”,每章的结构相似,先分要点讨论一些问题如编程的各个要素或一些编程问题,最后总结,思考,再提出一些深入思考等等,我们大可以每天随意的找一些有兴趣的东西读一读。
书中有很多生动,睿智之语,有一句话印象比较深刻:“构建软件就像犯罪一样:当有组织时,就会做得更好。”
个人认为,程序员不要一味学习高深的编程技术,要更加关注编程的思想,方法,态度,这本书绝对适合放在案头,偶尔翻一翻,提醒自己是否像优秀的程序员一样思考和行动。当然了知易行难,将这些思想,方法,态度应用在实际工作中是一个很困难或“痛苦”的过程,但如果可以坚持实践,并结合自己进行深入思考,这样定可以向传说中理想的程序员前进。
附注,网上有很好的读书笔记,做的很仔细很完整,推荐,如果没有时间阅读本书,可以直接阅读这份笔记,网址如下:
http://www.cnblogs.com/wing011203/tag/编程匠艺/

2010年1月5日星期二

扫除你的“垃圾代码”

背影1 最近忙着充电,写读书笔记,很久没写一些关于技术的东西,今天想谈一下最近的编码过程中的一个小感悟。七条广为人知的评判代码质量的基本原则:内聚性、松耦合、零重复、封装、可测试性、可读性以及单一职责。但是,长时间的编程过程中,因为自身长期以来积累的“恶习”,在编程过程中常常并不是真正注意并遵守这些原则。最近在重构一个我很久以前用C++编写的大概1万行左右的小项目,虽然代码整体架构还算OK,这次重构不算是伤筋动骨,只是一些小手术--很多的工作在想尽办法消除代码中的重复,在重构的过程中,发现自己写的“垃圾代码”非常多,真是很“汗颜.....”。

这次重构的过程中使用了一些《软件开发沉思录》里的关于“用精炼的代码表达出简单而优美的抽象”的一些规则,现在重构结束后总算是深有感触,这些规则虽然看似简单,但是如果身体力行有一定的难度,当坚持大部分规则后(暂时实在无法坚持所有规则),代码的质量和可读性会有一个质的飞跃,这也非常大的减轻了后面测试的压力。现在想想,我们给代码测试人员留下的很多bug其实就是我们我们编码“不规范”(没有注意“精炼的代码”的重要性,也就是没有重视编程“简单性”原则)造成的。

 

这里对书中这九条规则做出简单的摘要,和大家一起分享:

规则1:方法只使用一级缩进

一个常见的原则是将方法的行数控制在5 行之内。如果每个方法都只关注一件事,而它们所在的类也只做一件事,那么你的代码就开始变化了。由于应用程序中的每个单元都变得更小了,代码的可重用性开始指数增长。一个100 行的,肩负五种不同职责的方法很难被重用。如果一个很短的方法在设置了上下文后,能够管理一个对象的状态,那么它可以应用在很多不同的上下文中。在这样短小的代码段中查找bug 通常会更加容易。

规则2:拒绝else关键字

适当地使用多态;

规则3:封装所有的原生类型和字符串

规则4:一行代码只有一个“.”运算符

规则5:不要使用缩写

缩写会令人迷惑,也容易隐藏一些更严重的问题。尽量保持类名和方法名中只包含一到两个单词,避免在名字中重复上下文的信息。

规则6:保持实体对象简单清晰

这意味着每个类的长度都不能超过50 行,每个包所包含的文件不超过10 个。代码超过50行的类所做的事情通常都不止一件,这会导致它们难以被理解和重用。小于50行代码的类还有一个妙处:它可以在一屏幕内显示。

随着类变得越来越小,职责越来越少,加之包的大小也受到限制,包中的类越来越集中,它们能够协作完成一个相同的目标。包和类一样,也应该是内聚的,有一个明确的意图。保证这些包足够小,就能让它们有一个真正的标识。

规则7:任何类中的实例变量都不要超过两个

不断应用这条规则,可以快速将一个复杂的大对象分解成为大量简单的小对象。

规则8:使用一流的集合

任何包含集合的类都不能再包含其他的成员变量。每个集合都被封装在自己的类中,这样,与集合相关的行为就有了自己的家。

规则9:不使用任何Getter/Setter/Property

如果可以从对象之外随便询问实例变量的值,那么行为与数据就不可能被封装到一处。

其实以上的9个规则就是关于“编程简单性”一个小总结,努力地拥抱“简单性”,那么开发过程会变得更愉快,代码质量提高了,测试的花费就会少了很多,其实项目的整体开发速度也会相应的提高。

PS:今天的配图,是我最近在网上看到的,个人很喜欢的一张照片,感受阳光,简单,舒服,一切都恰到好处。

2009年11月24日星期二

挖概念:RTP timestamp, payload, RTP, RTSP, MPEG ES, TS, PS

阅读别人写的程序绝对是一个很好的提高编程水平的途径,同时也是一个快速获取新知的方式。但是如何从源程序的一点“新的知rtp_timestamp_braindiagram 识点”拓展到一个大的“知识面”,这是一个很有意思的问题,今天我想结合自己的小小的经验来谈谈这个问题。
我采用土法炼钢:“挖概念”法,就是从一个小的概念不停地向外延伸,直到对整个知识架构有一定程度的了解。
举一个我自己的例子,前不久我在看一个视频接收程序的源代码,看到有一行关于timestamp的计算,什么是timestamp这就是一个“新的知识点”。然后我就通过这个点挖出一些我需要的知识,几小时我慢慢地对整个知识架构有一定程度的了解,图示说明了我“挖概念”的过程,以下选载一些我找到的知识点,它们都是围绕着timestamp这个问题展开的。

[1]:


时间戳(Timestamp)-->在RTP中反映RTP数据信息包中第一个字节的采样时刻(时间)。接收端可以利用这个时间戳来去除由网络引起的信息包的抖动,并且在接收端为播放提供同步功能。
时间戳字段是RTP首部中说明数据包时间的同步信息,是数据能以正确的时间顺序恢复的关键。时间戳的值给出了分组中数据的第一个字节的采样时间(Sampling Instant),要求发送方时间戳的时钟是连续、单调增长的,即使在没有数据输入或发送数据时也是如此。在静默时,发送方不必发送数据,保持时间戳的增长,在接收端,由于接收到的数据分组的序号没有丢失,就知道没有发生数据丢失,而且只要比较前后分组的时间戳的差异,就可以确定输出的时间间隔。RTP规定一次会话的初始时间戳必须随机选择,但协议没有规定时间戳的单位,也没有规定该值的精确解释,而是由负载类型来确定时钟的颗粒,这样各种应用类型可以根据需要选择合适的输出计时精度。(来自:百度百科+Wiki)


[2]:


RTP实时传送协议Real-time Transport Protocol由两个紧密链接部分组成: RTP ― 传送具有实时属性的数据;RTP 控制协议(RTCP)。
RTCP的一个关键作用就是能让接收方同步多个RTP流,例如:当音频与视频一起传输的时候,由于编码的不同,RTP使用两个流分别进行传输,这样两个流的时间戳以不同的速率运行,接收方必须同步两个流,以保证声音与影像的一致。为能进行流同步,RTCP要求发送方给每个传送一个唯一的标识数据源的规范名(Canonical Name),尽管由一个数据源发出的不同的流具有不同的同步源标识(SSRC),但具有相同的规范名,这样接收方就知道哪些流是有关联的。而发送方报告报文所包含的信息可被接收方用于协调两个流中的时间戳值。发送方报告中含有一个以网络时间协议NTP(Network Time Protocol)格式表示的绝对时间值,接着RTCP报告中给出一个RTP时间戳值,产生该值的时钟就是产生RTP分组中的TimeStamp字段的那个时钟。由于发送方发出的所有流和发送方报告都使用同一个绝对时钟,接收方就可以比较来自同一数据源的两个流的绝对时间,从而确定如何将一个流中的时间戳值映射为另一个流中的时间戳值。RTSP实时流放协议(Real-Time Streaming Protocol)提供控制多种应用数据传送的功能,提供一种选择传送通道的方法,例如UDP, TCP, IP多目标广播通道,以及提供一种基于RTP协议的递送方法。

RTSP将工作在RTP的上层,用来控制和传送实时的内容。RTSP能够与资源保留协议一起使用,用来设置和管理保留带宽的流式会话或者广播。

(来自:百度百科+Wiki)

[3]:


读了RTP的说明(RFC 2250)后发现RTP with payload type 32 (MPEG1/2 video ES): The RTP timestamp 表示了video frame的PRESENTATION time。
RTP with payload type 33 (MPEG2 TS): The RTP timestamp 表示RTP packet的TRANSMISSION time。(来自:RFC 2250)


[4]:


MPEG一2标准的正式名称为“ISO/IEC13818信息技术——活动图象和相关声音信息的一般编码方法”,是其中的一个编码标准,主要是用于传输声音、图象数据压缩标准,它是MPEG-1的进一步发展,码流在1.5Mb/S到50Mb/s之间。

MPEG-2标准是将视、音频及其他数据基本流(ES)组合成一个或多个适宜于存储或传输的数据流的规范。MPEG-2分为压缩层和系统层:压缩层中,数字视音频数据分别经过编码器,生成连续不分段的基本码流(ES),对于视音频来说,ES就是由一系列编码后的视音频帧的存取单元(AU)组成;在系统层将视音频ES分别通过各自的打包器,加上相应的包头,打包分组为包长度可变的基本流(PES)。系统层主要用来描述视、音频数据复用和同步方式,节目复用器和传输复用器分别将视音频PES加入系统层信息组成相应的节目流(PS)和传输流(TS),多条TS流还可以再次复用成一条多节目TS(MPTS)。

1.数字化的视频、音频和辅助数据,经过压缩后形成各自的基本流(ES)。
2.视频和音频的ES流分别按一定的格式打包,构成具有某种格式的打包的基本信息流(PES:Packetized Elementary Stream),分别称为视频PES和音频PES。这一步骤在打包器内实现,PES的长度可在一定范围内变化。
3.将视频、音频的PES流以及辅助数据按不同的格式再打包,然后进行复用,即分别生成了TS流和PS流.
根据传输媒体的质量不同,MPEG-2中定义了两种复合信息流:传送流(TS:Transport Stream)和节目流(PS:Program Stream)原始的视音频数据流经编码器编码输出压缩后的基本码流 ES, 它含有解码器所必需的、用于恢复原始视音频的信息。 基本码流 ES分解打包成 PES数据包, 每个 PES包在复用的过程中被分成固定长度的传输流包( TS Packet)。TS包的长度是固定的,为188字节。
(来自:张佳,电影频道节目中心传送部,有线电视技术,MPEG-2码流的层次分析,2008,9)

-----------
“挖概念”法,有时候挺好用的,可以在短时间内对一个比较大的知识领域得到一定的理解。