2009年11月30日星期一

如何将BGR转成YUV420

今天心情非常之好,花了一个下午时间搞定了困扰了我一个月的事情。写了一个程序可以把openCV的BGR图像格式转换成YUV4:2:0,然后通过FFmpeg的API把YUV4:2:0的图像编码压缩,最后利用live555把压缩后的buffer打包成RTP包,最后用Unicast将它们发送出去。简单的说就是 BGR—>YUV4:2:0—>encode to buffer—>RTP—>Unicast这样的过程。

把预想的程序完成真是一大快事,随便记录一部分比较特别的东西,因为openCV没有BGR转YUV420的函数,所以需要自己写一个,我写的这个函数读入一个三通道的图像数据,然后先使用cvCvtColor函数,将BGR转成YCrCb,这个YCrCb是4:4:4也就是全抽样,然后对Cb和Cr分量进行抽样,最后我们可以得到三个数组,分别存Y, U, V三个分量。这个转换不算复杂,就是使用了以下的概念。

YUV4:2:0

 yuv420 4:2:0并不意味着只有Y,Cb而没有Cr分量。它指得是对每行扫描线来说,只有一种色度分量以2:1的抽样率存储。相邻的扫描行存储不同的色度分量,也就是说,如果一行是4:2:0的话,下一行就是4:0:2,再下一行是4:2:0...以此类推。对每个色度分量来说,水平方向和竖直方向的抽样率都是2:1,所以可以说色度的抽样率是4:1。对非压缩的8比特量化的视频来说,每个由2x2个2行2列相邻的像素组成的宏像素需要占用6字节内存。
八个像素为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3][Y5 U5 V5] [Y6 U6 V6] [Y7U7 V7] [Y8 U8 V8]
存放的码流为:Y0 U0 Y1 Y2 U2 Y3 Y5 V5 Y6 Y7 V7 Y8
射出的像素点为:[Y0 U0 V5] [Y1 U0 V5] [Y2 U2 V7] [Y3 U2 V7][Y5 U0 V5] [Y6 U0 V5] [Y7U2 V7] [Y8 U2 V7](来自百度百科)我们可以对每八个像素:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3][Y5 U5 V5] [Y6 U6 V6] [Y7U7 V7] [Y8 U8 V8]进行一次抽样,抽样: Y0 Y1 Y2 Y3 Y4 Y5 Y6 Y7 给Y分量,U0 U2 给U分量,V5 V7给V分量。

现在提供另一个简单的转换函数:

void convertBGR2YUV420(IplImage *in, unsigned char* out_y, unsigned char* out_u, unsigned char* out_v)  
{  
    // first, convert the input image into YCbCr 
    IplImage *tmp = cvCreateImage(cvSize(in->width, in->height), 8, 3); 
    cvCvtColor(in, tmp, CV_RGB2YCrCb); 
    /*  
    * widthStep = channel number * width  
    * if width%4 == 0  
    * for example, width = 352, width%4 == 0, widthStep = 3 * 352 = 1056  
    */ 
    int idx_in = 0;  
    int idx_out = 0;  
    int idx_out_y = 0;  
    int idx_out_u = 0;  
    int idx_out_v = 0; 
    
    for(int j = 0; j < in->height; j+=1) { 
        idx_in = j * in->widthStep; 
    
        for(int i = 0; i < in->widthStep; i+=12) { 
        // We use the chroma sample here, and put it into the out buffer 
        // take the luminance sample 
        out_y[idx_out_y] = tmp->imageData[idx_in + i + 0]; // Y 
        idx_out_y++; 
        out_y[idx_out_y] = tmp->imageData[idx_in + i + 3]; // Y 
        idx_out_y++; 
        out_y[idx_out_y] = tmp->imageData[idx_in + i + 6]; // Y 
        idx_out_y++; 
        out_y[idx_out_y] = tmp->imageData[idx_in + i + 9]; // Y 
        idx_out_y++; 

        if((j % 2) == 0) { 
              // take the blue-difference and red-difference chroma components sample  
              out_u[idx_out_u++] = tmp->imageData[idx_in + i + 1]; // Cr U  
              out_u[idx_out_u++] = tmp->imageData[idx_in + i + 7]; // Cr U  
              out_v[idx_out_v++] = tmp->imageData[idx_in + i + 2]; // Cb V  
              out_v[idx_out_v++] = tmp->imageData[idx_in + i + 8]; // Cb V 
        }  

        }    
    }  
    cvReleaseImage(&tmp); 
}

杜塞多夫,科隆,波恩两日游

上周五,六,作导游陪国内来的同学逛了逛杜塞多夫,科隆,波恩(今年至少去了5次杜塞,每次去都是阴天下雨,这次也不例外)。还是采用“我的经典旅游路径”:去了杜塞老城,国王大道,莱茵河畔,波恩市政厅,皇宫,贝多芬故居,老城,科隆大教堂,巧克力博物馆。随手照了几张照片。

100_3397 杜塞老城的骑警。

100_3399 在杜塞老城看到的彩虹。

100_3402 波恩的圣诞市场。

100_3415 100_3427科隆的巧克力博物馆展示的巧克力生产线一角。

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)

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

2009年11月23日星期一

浅议SDP(会话描述协议)

因为最近常常使用到SDP(会话描述协议Session Description Protocol)写了一些SDP的文本,在linux里使用例如"ffplay test.sdp"来播放媒体流,今天想简单谈谈SDP,做了一个小小的总结和分析,希望对大家有帮助。

SDP是描述的是流媒体的初始化参数,IETF对其的描述可以在RFC 2327找到,SDP是一个纯文本文档,后缀为.sdp,它的基本内容包括:

# 会话信息:
* 会话名和目的;
* 会话时间;
* 会话使用的带宽;
* 会话的用户信息;
# 媒体信息:
* 媒体类型,例如:视频或音频;
* 传输协议,例如:RTP/UDP/IP;
* 媒体格式,例如:H.263视频或者MPEG视频;
* 多播地址和媒体传输端口(IP多播会话);
* IP单播会话的联系地址和媒体传输端口

举一个例子来进行分析(live555的testMPEG1or2VideoStreamer里附加的SDP文本):

----------------------------------------------------------------------------------------------

v=0

o=- 49451 3 IN IP4 127.0.0.1

s=Test MPEG Video session

i=Parameters for the session streamed by "testMPEG1or2VideoStreamer"

t=0 0

a=tool:testMPEG1or2VideoStreamer

a=type:broadcast

m=video 1234 RTP/AVP 32

c=IN IP4 239.255.42.42/127

----------------------------------------------------------------------------------------------

可以发现SDP会话描述由许多文本行组成,它的格式为“类型=值”。其中v,o,s等等代表了是类型。

第1行v代表了协议版本,例子中为0。
第2行o代表所有者/创建者和会话标识符。

第3行s代表会话名称,例子中为Test MPEG Video session,用户可以自己填写。
第4行t代表会话活动时间。

第5行和第6行a代表会话属性行,可写0个或多个。

第7行m代表代表媒体信息;video代表是视频流;1234代表UDP端口号是1234;RTP/AVP指媒体传输协议使用RTP/AVP;32代表媒体格式使用MPV并且使用90KHz的时钟。关于RTP/AVP可以在RFC 3551 RTP A/V Profile July 2003找到。以下是部分截取:

PT encoding media type clock rate
name (Hz)24 unassigned V
25 CelB V 90,000
26 JPEG V 90,000
27 unassigned V
28 nv V 90,000
29 unassigned V
30 unassigned V
31 H261 V 90,000
32 MPV V 90,000 (这就是例子中的RTP/AVP类型)
33 MP2T AV 90,000
34 H263 V 90,000
35-71 unassigned ?
72-76 reserved N/A N/A
77-95 unassigned ?
96-127 dynamic ?
dyn H263-1998 V 90,000

第8行c代表连接信息。

当然了还可以加上更多的信息描述,例如b=AS:104857,b代表了带宽信息。

2009年11月22日星期日

除了编GUI,Qt还可以做很多事

昨天和学长一起讨论一个旧的项目,他看到我的project里大量使用了Qt的库,说Qt不是做GUI的吗,在编一些非GUI的东西你用它们干嘛(看来这是很多人的既定观点,真的需要改变一下),我说Qt不仅只是可以做GUI还可以完成很多东西,并且很强大,绝对不会比你常用的库弱。整个项目使用Qt的库还可以在一定程度上统一编程风格,并提高代码质量和减少维护开销,Qt库也提供很好的线上资源,很方便查阅。我这里举三个很简单的例子,说明Qt库的一些其他特性。

信号和槽

正如CObject是大多数MFC类的根类或基类,QObject是Qt的基类。先在我们的类继承QObject,并在类声明中使用的Q_OBJECT宏,我们就可以使用QObject信号和槽的强大的机制。
QObject信号和槽即简单又好用,现在举一个简单的例子,在例子中使用QTime和QObject信号和槽,每100微妙对象间自动进行一次接口的数据交换。

/* myclass.h */
#include <QObject> 
#include <QTime> 

class MyClass : public QObject { 
    Q_OBJECT 
public: 
    MyClass(); 
    int data_in; 
    int data_out; 

private slots: 
    void dataExchangePipe(); 

private: 
    AnotherClass anotherclass; 
    QTimer *poller; 
};
 
 
/* myclass.cpp */
MyClass::MyClass() { 
    object = new AnotherClass; 
    poller = new QTimer(this); 
    connect(poller, SIGNAL(timeout()), this, SLOT(dataExchangePipe())); 
    poller->start(100); // the time to poller 
}
 
void MyClass::dataExchangePipe() { 
    data_in = object->data_out; 
    object->data_in = data_out; 
}

容器,迭代器

还在使用STL容器吗,也许Qt也是一个很好的选择,Qt提供了QVector<T>, QLinkedList<T>,QList<T>QMap<K,T> and QHash<K,T>容器。Qt提供对容器的两种风格的迭代:Java风格的迭代器和STL风格迭代器

Java风格(非常简单,Qt官网上的一个例子)

QList<QString> list; 
list << "A" << "B" << "C" << "D"; 
QListIterator<QString> i(list); 
while (i.hasNext()) 
    qDebug() << i.next();


STL风格(比较powerful,每一个sequential container class C<T>都有两个 STL-style 迭代器类型: C<T>::iterator 和 C<T>::const_iterator,以下是Qt官网上的例子)

QList<QString> list;  
list << "A" << "B" << "C" << "D";  
QList<QString>::iterator i = list.end(); 
while (i != list.begin()) { 
    --i;
    *i = (*i).toLower(); 
}

多线程

Qt的QThread很类似C++Boost线程库。使用起来也非常容易,例如:

建thread:

#include <QThread> 
class MyThread : public QThread { 
  public: void run();  
}; 

void MyThread::run() { 
... 
... 
}

使用thread,很简单:

先声明对象,MyThread *mythread = new MyThread;

启动: mythread->start();

终止: mythread->terminate();

对线程的同步,Qt也提供了QMutex;我们可以声明QMutex mutex;然后使用mutex.lock();和mutex.unlock();进行线程同步。

更多有用的特性请查阅:http://doc.trolltech.com/4.5/index.html

2009年11月16日星期一

源代码行数计算器

刚刚完成了一个项目的重构。使用源代码计算器一查,代码少了30%,系统的结构也更清晰了。总结经验,因为,抛弃了很多原型代码。改变以前自上而下的设计方法,使用了自下而上的设计方法,并把一些绝对要做的模块做了,然后再把它们搭起来。 还加入了一些Facade降低了一些模块间的耦合。重写了一些utility classes(工具类),然后就可以让大量的类使用某个给定的类,达到了《代码大全》所谓的high fan-in(高扇入)。《代码大全》中提到了应该让每个类都应该少或适中使用其他的类(不要超过7个)这就是所谓的低扇出,low fan-out,在我重构的时候也努力的遵守这个原则,这大大减低了类的复杂度。

呵,做这个项目走了不少弯路,看来还是要多读书,并结合一些编程经验带着问题读,多思考。这样会提高编程的质量,并提高效率。

line counter

想计算所编软件源代码共有几行,网上搜到了n种计算器,随便找了一种,使用了line counter,还不错,该软件下载地址:http://sourceforge.net/projects/lcounter/

2009年11月5日星期四

电影:圣诞颂歌-Eine Weihnachtsgeschichte

3D 版的“人物动作捕捉”技术的动画片圣诞颂歌(A Christmas Carol,德语:Eine Weihnachtsgeschichte)在德国上映的第一天就跑到电影院尝鲜,第一次看“人物动作捕捉”动画片的3D版,个人感受相当不错,某些场景还挺震撼的。

disney-s-eine-weihnachtsgeschichte-2008-imagemovers 值得一提的是金·凯瑞(Jim Carrey)一人分饰多角,在影片中饰演守财奴艾柏纳泽·斯克鲁奇(Ebenezer Scrooge)从儿童到老年的四个时期的角色,还有圣诞三精灵。

如果想回忆一下的查尔斯·狄更斯的原著内容:在http://publicliterature.org/可以找到A Christmas Carol的有声书和原著的pdf。http://publicliterature.org/books/christmas_carol/xaa.php

enjoy!

2009年10月30日星期五

读书:风云《游戏之旅-我的编程感悟》

对于一本技术图书的好坏我有两个最简单的评断标准,一是是否能吸引人,这本书就像一个好游戏一样让人沈浸其中,不能自拔,上个一个周末分“两口气”把它仔细读完了。第二个标准就是我是否做很多笔记,在读这本书的过程中我在我的Google docs做了非常多的笔记。以我的标准来说这本书绝对是五星级的好书。虽然我不是游戏程序员,但是我也得到了非常多的收获,我不仅从风云的亲切自然的文笔中了解他的一些成长经历,而且还“似乎”进入了一个陌生但是令人兴奋的游戏设计领域。通过阅读这本书还有一个收获,就是进行了一些关于游戏开发的方法,游戏引擎,游戏引擎中的优化等等知识的“扫盲”。
s2198972 “第二章算法,程序的灵魂”是我最喜欢的章节。如何进行算法层面的优化,本书也谈到了几点,看完深有体会,以下是部分书摘。

1 数学方法的改进

2 预运算来节省时间(空间换时间来避免重复运算)或是重复运算来节省时间。

3. 简化算法求得近似解来取代精确解(或最优解)

4. 改进数据组织方式,用更少的操作处理更多的数据,甚至避免冗余数据的处理

我也非常喜欢“第九章C和C++”和“第十章 调试”,强烈推荐仔细阅读。我们使用C和C++,但是有没有在不同的层面去*思考*一下它们而不是仅仅使用它们,书中关于C和C++的一些精彩诠释也令我得到很多启发,以下是部分书摘:

“C++和C一样,都是最贴近计算机固有模型的,C++的每一行代码,都和C一样有明确的目的,没有去干多余的事情。”

“C语言是对汇编语言的一种抽象,正如汇编语言是对机器底层指令的一个最小幅度的抽象一样,是为了人类能更方便地控制机器。但是,人在使用C语言控制机器时,并没有比汇编语言有观念上的不同,也就是说,程序员依旧是按机器模型来思考问题。在面对问题时,问题的描述往往并不符合机器思考的模型,程序员的工作也就是在两者之间搭上一座桥梁,让问题的描述可以用机器的思维来解决。”

“C++则采用了另一套方法来减轻程序员解决问题的复杂度,那就是面向对象。它是一种通用的方法,面对问题,我们把问题中的事物都表示为对象,对象有自己的行为,对自己遇到的问题提出符合机器模型的解决方案,或把自己划分为更小的对象集合。这样,程序员就可以在对象的级别用对象本身的术语描述问题,而这些术语最终都会被直接对应到机器模型上。”

八卦一下,书中有一些关于一些编程语言的有趣观点:

“JAVA更像一个实验室研究出来,有着浓厚的学术气味,外观优美超过它本来应该具有的人们所期望的实用性。”

“而C#则是工程师的结晶,外加了许多商业味道,被微软强烈地推广。”

“脚本语言如Python会像C++这样的主体开发语言的一种有益的补充。”

风云来自实践的这本书,是我读过最有趣生动,亲切的本土技术图书,同时也是一本极好的教材。

在这本书中,我最有感触并最喜欢的一句话是“多年的编程经历让我明白了一个道理:绝大多数情况下,没有解决不了的问题,只有因为平时缺少练习而惧怕问题的复杂度,畏惧的心理让我们选择避让,采取并不那么好的方案去解决问题。”一句很平实的大实话。真正的“高手”之路,无论什么领域,其实就是多思考,多动手,知难而进吧。

2009年10月25日星期日

读书:梁肇新《编程高手箴言》

s2301239上个星期读了超级解霸的作者梁肇新的这本书。这本书行文亲切自然,国人写的书显然比国外的图书的中文版更具有“亲和性”。当然了,也许也会有很多人对文中的一些观点有不同的意见,但是不同的人一定会得到不同的启发,绝对值得一读。带着自己的思考和结合自身编程的实践经验去阅读这本书也许可以得到更多的收获。这里谈谈我从中得到的一点启发。
曾经参与一个项目开发经过多次失败,最后才发现对于实际应用软件项目,最适合的方法就是类似“实现核心-->增强功能-->测试”这样一个过程。强烈赞同书中的观点:
“我们不可能一开始就把所有的细节都考虑清楚,通常,开始只需要一个基本的框架,也就是核心的核心。如果这个核心都实现不了,即使再进行下去,整个项目也都是多余的,只是浪费时间而已。所以,一开始就要解决最核心的问题。当最核心的问题解决之后,就可以进行第二阶段。我们把软件开发分为设计规划阶段,功能设计阶段,测试阶段。所有的产品都可以划分为这三个阶段。事实上,如果最核心部分实现了,那么依赖于此核心,再实现增强一点功能就是第二阶段。第二阶段完成后,就可以进行第三阶段。”
还有例如书中谈到的关于界面和接口的“箴言”:
“界面是界面部分,接口是独立的一块。接口部分就是程序的实现内核,通常先用Console方法直接地实现程序,这样更便于分离界面和内核。如果开始就用Windows的界面去实现内核部分,这样就可能会把界面和内核混在一起,无法分开。所以,一开始做程序时,就不要考虑界面是什么样子。”
“接口的目的就是把外部条件和内部条件分离开来。外部条件由别的程序提供,别人可以用。做接口最好的方法,就是直接用Console程序先实现程序的主体,然后从中提炼出接口来。这样做提炼会简单明了。如果用可视界面先实现了程序的功能后再去提炼,就会比较困难,因为这样就会把界面和功能实现混在一起,很难区分。”
看完后深有体会,我在做一些应用软件项目时总是把界面和功能实现混在一起,的确会造成维护,修改,甚至是阅读的很大困难,程序结构也会不够简单明了。
我总是太过局限于“软件工程的思维框架”中,常常在抽象化的时候,总是想要把所有的事情都想全了,常常做一个巨大无比的对象,这本书对此也提出一些让人有所反思的观点,例如:
“做对象化时还有一条准则,既对不同的应用去设计程序,而不是针对一种所谓的虚拟模型去设计一个对象。所谓的抽象化,就是想着将来有可能会用的某个功能。这样设计出的软件又庞大又耗时,运行也很慢。这就是杀鸡取卵(教科书会教你这种方法)。”
“杀鸡取卵就是指设计的软件提供了很多的功能,其实用到的功能只是那么一丁点。可能做了一个巨大无比的对象,但是只用到对象的很小的一些功能而已。因为做抽象化的时候,要把所有的事情都想全了,但是这是不现实的,实际上也是没有必要的,且是浪费的。如果凡是思考问题都是为了将来的大而全,这样出错的可能性也会成倍的提高。一般来说,代码越大,出错的地方也就越大,如果代码越小,出错的地方也就越小。将功能模块做成API函数与做出对象就不一样,做成一个对象时,该模块出错的机会就多一些,因为对象还有一个动态的过程,但做成一个函数时,调用函数就可以了,模块出错的机会就会比较小。所以,程序越简单,越不容易出错。”
书中的“程序主干和分支”的概念,也令人很有启发:
“核心就是整个系统中最重要,最基础,最简单的部分。首先要把程序的核心提炼出来,在给它添加附加的部分,它就能成为一个完整的软件。主干是最核心的核心,其后在添加的其他的部分就是枝叶了。”
“编程就是要保证核心的核心不出错,那剩下的枝叶都是一层一层地构建出来的。”
读《编程高手箴言》,也是自己编程思想,习惯等等的一个“自审”的过程。你一定可以找到适合自己需要的“箴言”,用它去提高自己吧。

2009年10月24日星期六

读书: 成寒《躺着学英语:听力从零到满分》

最近因工作时发现自己的英语口语水平还是停留在n年前水平,阔别多年后又开始自学英语了,偶得这本英语励志书,n年就前看过一些英语励志书,其实内容光看这本书的书名和简介,就可以猜到十之八九。但是,正如书中所说的“如果一个人没有纪律,没有办法自动自发学习,一定要人用鞭子打才肯用功的话,再多的英语励志书,也不过是一帖安慰剂罢了。”

s2212962 “学英语,用什么方法都可以。只要这个方法能够挑起学习的乐趣,就一定会有成果。”作者推荐大家用一种轻松快乐的心态去学英文,这本书的行文也很清新流畅,不像一般的英语励志书教导式的文笔,而是让你感觉到作者就在身边和你聊聊她的经验,在短短的几小时就可以很轻松的一口气读完。

我非常认可书中的一些观点例如“自修英语,最大的好处是可以随心所欲。尽量找好听的,自己感兴趣的教材”。至于如何选择适合自己的教材这本书谈到“每页的生词不超过十个,也不低于五个,便是合乎自己程度的教程”。

顺带一提,通过阅读这本书我还纠正了一个长期以来的错误认知,原来晶圆厂例如FAB1是Fabrication(精密制造)的缩写,而不是Factory,象征着晶圆厂与传统工厂的层次有别。