2010年11月

(转)【观点】风雨20年:我所积累的20条编程经验

作者Danylko是一位资深开发顾问,DCS公司的创始人。

从11岁时,我就一直在编程,并且一直都很喜欢技术和编程。这些年来,我积累了一些艰难又容易的经验。作为一名程序员,你或许还没这些经验,但我会把它们献给那些想从中学到更多的朋友。

我会持续更新这些经验,我可能还会有更多的感想,但就我这20年来看,我想下面这个列表中基本不需要增添额外的东西了。下面就是我至今最难忘的经验。

  1. 估算解决问题所需要的时间。

不要怕,承认吧!我曾见过一些程序员为了解决一个特殊问题而坐在显示器前面8小时。为自己定一个时间限制吧,1小时、30分钟或甚至15分钟。如果在这期间你不能解决问题,那就去寻求帮助,或到网上找答案,而不是尝试去做“超级堆码员”。

  1. 编程语言是一种语言,只是一种语言。

随着时光推移,只要你理解了一种语言的原理,你会发现各种语言之间的相似之处 。你所选择的语言,你应该觉得“舒服”,并且能够写出有效(而且简洁)的代码。最重要的,让语言去适应项目,反之亦然。

  1. 不要过于注重程序的“设计模式”。

有时候,写一个简单的算法,要比引入某种模式更容易。在多数情况下,程序代码应是简单易懂,甚至清洁工也能看懂。

  1. 经常备份代码。

在我年轻时,我就有过因硬盘故障而丢了大量代码的经历,这经历很恐怖的。只要你一次没有备份,就应当像有着严格的期限,客户明天就需要。此时就该源码/版本控制软件大显身手了。

  1. 承认自己并不是最顶尖的程序员 - 知不足。

我常想,我对编程了解已足够多,但是总有其他人比你优秀。正所谓,“一山总比一山高”。所以,向他们看齐吧!

  1. 学习再学习。

正如第5点所说,我经常会在手里拿一本计算机或编程相关的杂志或书(不信,可以问我的朋友)。诚然,总有很多你不知道的技术,你可以从中学习以保持不落后。如果你有一种灵巧的方式来获取你需要的新技术,那你每天都应该坚持学习。

  1. 永恒的变化。

你对待技术/编程知识,就应像你对待股票一样:多样化。不要在某一特定技术上自我感觉良好。如果那种技术或语言已经没有足够支持,那你还不如现在就开始更新你的简历,并启动培训新计划。我能保持前行的主要原则是什么呢?至少了解两到三种语言,所以,如果某种语言过时了,你在学习新技术的时候还可以依靠另一种语言。

  1. 提携新人。

协助并且培养初级/入门的开发人员学习优秀的编程方法和技巧。也许你还不知道,在帮助他们向更高一层前进时,你自己也在向更高一层提升,你会更加自信。

  1. 简化算法。

代码如恶魔,在你完成编码后,应回头并且优化它。从长远来看,这里或那里一些的改进,会让后来的支持人员更加轻松。

  1. 编写文档。

无论是Web服务的API,还是一个简单的类,你尽量编写相应文档。我曾经引以为豪的代码注释,因过度注释而有人指责。给三行代码加一行注释,只需要你几秒时间。如果那是一个比较难以理解的技术,千万别担心过多注释。如果你能很好做好自己的工作,大多数架构师、后备程序员、支持组都会感激你。

  1. 测试、测试再测试。

我是一名黑盒测试粉丝。当你完成编码后,你“被认可”的时候就开始了。如果你们公司有QA部门,如果你的代码中有错误,那你得到的评论,会比项目经理还多。如果你不彻底测试自己的代码,那恐怕你开发的就不只是代码,可能还会声名狼藉。

  1. 庆祝每一次成功。

我见过很多程序员在解决编程技术难题后,会和同伴握手、击掌或甚至手舞足蹈。每个人在生命中都会碰到“顿悟”。如果一个程序员高兴地跑来叫你去看他的非凡代码,也许你已经看过这样的代码100遍了,但你也应该为了这个家伙而庆祝第101次。

  1. 经常检查代码。

在公司,你的代码要经常检查(包括自查和其他同事检查)。不要把别人的检查,看成是对代码风格的苛求。应该把它们看作是有建设性的批评。对个人来说,经常检查你的代码并且自问,“我怎样才能写得更好呢?” 这会让你加速你的成长,让你成为一个更优秀的程序员。

  1. 回顾你的代码。

在看到自己以前的代码时,通常会有两种方式:“难以至信,这代码是我写的”和“难以至信,这代码是我写的”。第一种往往是厌恶的语气,并在想如何改进它。你也许会惊叹,旧代码也能复活成为一种更好的程序,甚至是一个完整的产品。第二种通常带着惊奇和成就感。开发人员应该一到两个自己完成的项目成果,能让众人不禁而立并注目而观的项目。同样,基于你优越的编程能力,你可以把过去的程序或项目拿出来,把它们更新为更加优秀的产品或想法。

  1. 幽默是不可缺的。

在我20年的开发生涯中,我还没有碰到哪位程序员是没有幽默感的。实际上,干我们这行,幽默是一项必备品。

  1. 谨防那些无所不知的程序员,不愿分享的程序员,还有经验不足的程序员。

当你遇到这几种程序员时,你自己要谦虚。无所不知的程序员,更想当一个英雄而不是团队成员;保守的程序员则是在编写着他们独享的代码;而经验不足的程序员则会每十分钟就来问你一下,当代码完成后,代码已经是你的,而不是他们。

  1. 任何项目都不会那么简单。

朋友、家人和同事曾请求我仓促做一些事情,仓促做一个程序或者网站。对于这样的事,应该从双方做计划,才能做出令两方都会满意的东西。如果某人起初只是需要一个使用Microsoft Access的、只有有3个页面的网站,但来就很可能变成一个有15个页面的网站,并使用SQL Server,有一个论坛,还有一个定制的CMS(内容管理系统)。

  1. 任何时候不要想当然。

假如你承接一个简单的项目,你可能会认为某个部分可以轻松完成。千万别这样想!除非你有一个类、组件、或者一段已经写好的代码,并且在现有的项目已经测试通过。不要认为这将是很容易的。

  1. 没有已经完成的软件。

曾经有一位程序员告诉我,没有软件是已经完成的,它只是“暂时完成了”。这是明智的忠告。如果客户还在使用你写的程序,并经受了时间的考验。如果有机会,你仍在更新它,这并不是什么坏事,这让你不断地前行。

  1. 耐心是一种美德。

当客户、朋友或家庭成员用电脑的时候,他们也许会受挫,进而想砸电脑,或气冲冲地离开。我一直在告诉他们,“是你掌控电脑,不是电脑掌控你。”对于用作编程的电脑,你要有一定的耐心。一旦程序员知道问题所在后,他们就会站在电脑的角度看问题,并且说“哦,这就是为什么它是这样做。”

英文出自:http://www.dcs-media.com/Archive/20-20-top-20-programming-lessons-ive-learned-in-20-years-FH

基于C API的SQLite3基本数据库操作

  最近闲来无事,就写写博文来打发时间吧。。。

  这里把近期几个项目中总结下来的SQLite数据库使用经验归纳一下,共享出来,一来便于自己查阅,二来也便于和朋友们一起互相学习。

  这里先简单介绍一下SQLite数据库,给未接触过的朋友扫扫盲:
  SQLite是一款开源嵌入式文件型数据库,这个主要是和其他一些C/S架构的关系型数据库比较而来的,比如MySQL等。

  说他是嵌入式,因为SQLite的所有功能全部包装在一个dll中,我们只需要使用其中的导出接口就可以操作SQLite数据库,这样使得数据存储功能能够很方便的集成进用户的程序中,并运行在客户程序的进程空间中

  说他是文件型,因为SQLite的数据库文件就是一个独立文件(SQLite本身不限制数据库文件的扩展名),再没有其他的了,数据,表结构,查询,视图等等都保存在这个数据库文件中,不会依赖任何数据库环境

SQLite的主要特点:

  1. 无需部署,0配置,无服务端
  2. 跨平台
  3. 数据文件管理方便
  4. 较完善的SQL92标准支持,SQLite基本实现了SQL92标准,其他的一些不兼容的地方,可以参看官方相关说明,链接:http://www.sqlite.org/omitted.html
  5. SQL语句执行速度快,具体的对比数据,网上的评测有很多,这里就不多说了
  6. 应用较广。最著名的集成应该数Android了吧,其他的PHP ,Python等都做了集成,所以还是很不错的
  7. 完美的Unicode编码支持。SQLite的接口中,凡是涉及字符串的都是用UTF8或UTF16编码交互,有的同时提供这两种编码的接口函数,所以多语种支持绝对不是问题(这也是我偏爱SQLite的很重要的原因,呵呵)。

  好了,下面说说基于C API的SQLite操作,这里谢绝讨论其他的一些包装库,这不是本文的范围,当然SQLite的接口包装库函数还是很多的,基本覆盖各种主流开发语言。如基于Object Pascal的sqlitesimpledelphi,想了解这个库的朋友可以查看我的其他博文:

Delphi 2010下使用sqlitesimpledelphi连接SQLite数据库及中文乱码问题的解决
sqlitesimpledelphi修正以支持Unicode,Delphi 2010 测试通过

SQLite的数据库操作其实和常规的数据库操作流程是一样的:

  1. 连接数据库。
  2. 构造SQL语句并执行
  3. 对于SELECT语句,可以获取查询结果
  4. 数据库使用完毕之后,关闭数据库

  这里说明一下,下面所介绍的函数不会涵盖所有的API函数,毕竟SQLite针对同一个功能点提供了不同的API函数,主要表现在参数和配置功能上,有需要深入了解的朋友可以参考官方的文档。

打开数据库:

API函数:

int sqlite3_open(
const char *filename,   /* 数据库文件路径(UTF-8编码) */
sqlite3 **ppDb          /* 输出: SQLite 数据库句柄 */
);
int sqlite3_open16(
const void *filename,   /* 数据库文件路径(UTF-16) */
sqlite3 **ppDb          /* 输出: SQLite 数据库句柄 */
);

如果调用成功会返回SQLITE_OK,否则返回错误码。

构造SQL语句

这里就不多说了,这和SQLite本身无关,可以根据需要使用适当的方法构造即可,注意传给SQLite函数的时候,字符串编码要记得转换为UTF8/UTF16

执行SQL语句。

在SQLite中执行SQL语句比较简单的方法是调用函数:

int sqlite3_exec(
sqlite3*,                                  /* 打开的数据库句柄 */
const char *sql,                           /* UTF8编码的SQL语句 */
int (*callback)(void*,int,char**,char**),  /* 回调函数,对于SELECT语句返回的结果处理在回调函数中进行 */
void *,                                    /* 传递给回调函数的参数 */
char **errmsg                              /* 相关错误信息 */
);

其实sqlite3_exec只是封装了sqlite3_prepare、sqite3_step(即SQL中的预编译技术)的,主要目的是为使用者提供方便,笔者个人觉得使用后者会更加好一点,这里先做一下总结:

int sqlite3_prepare(
sqlite3 *db,            /* 打开的数据库句柄 */
const char *zSql,       /* UTF8编码的SQL语句,可以参数化 */
int nByte,              /* SQL语句的字节长度,可以传递-1,即字符串以\0结尾 */
sqlite3_stmt **ppStmt,  /* 输出:预编译之后的SQL语句句柄 */
const char **pzTail     /* 输出: 指向zSql缓冲区中跳过有效SQL字符串的第一个字节 */
);

int sqlite3_prepare_v2(
sqlite3 *db,            /* 打开的数据库句柄 */
const char *zSql,       /* UTF8编码的SQL语句,可以参数化 */
int nByte,              /* SQL语句的字节长度,可以传递-1,即字符串以宽字符\0结尾 */
sqlite3_stmt **ppStmt,  /* 输出: 预编译之后的SQL语句句柄 */
const char **pzTail     /* 输出: 指向zSql缓冲区中跳过有效SQL字符串的第一个字节 */
);

int sqlite3_prepare16(
sqlite3 *db,            /* 打开的数据库句柄 */
const void *zSql,       /* UTF16编码的SQL语句,可以参数化 */
int nByte,              /* SQL语句的字节长度,可以传递-1,即字符串以宽字符\0结尾 */
sqlite3_stmt **ppStmt,  /* 输出: 预编译之后的SQL语句句柄 */
const void **pzTail     /* 输出: 指向zSql缓冲区中跳过有效SQL字符串的第一个字节 */
);

int sqlite3_prepare16_v2(
sqlite3 *db,            /* 打开的数据库句柄 */
const void *zSql,       /* UTF16编码的SQL语句,可以参数化 */
int nByte,              /* SQL语句的字节长度,可以传递-1,即字符串以宽字符\0结尾 */
sqlite3_stmt **ppStmt,  /* 输出: 预编译之后的SQL语句句柄 */
const void **pzTail     /* 输出: 指向zSql缓冲区中跳过有效SQL字符串的第一个字节 */
);

其中带参数的SQL语句可以这样定义参数:
?
?NNN
:VVV
@VVV
$VVV
参数编号从1开始,例如:INSERT into db values(?1, ?2)

v2版本函数时SQLite根据需要添加的增强函数,新的程序推荐使用v2版本函数,只需与原来函数的区别,可以参考官方原文

int sqlite3_step(sqlite3_stmt*);
执行一次预编译SQL语句,在这之前,如果SQL是参数化的,可以调用sqlite3_bind来绑定数据,int、string、blob等等
如果执行成功会返回SQLITE_DONE,如果查询有结果会返回SQLITE_ROW,并可以通过API获取结果中的第一行数据,需要获取下一行数据可以再次调用sqlite3_step直到返回SQLITE_DONE表示后面没有数据了
如果需要重新对预编译的SQL绑定数据并执行,需要先reset一下,然后再调用step,即函数:
int sqlite3_reset(sqlite3_stmt *pStmt);

下面是绑定数据到预编译SQL语句的相关函数:
以下函数的第一个参数指代预编译的SQL句柄,第二个参数指代绑定的参数编号,对应于参数化的SQL语句中的参数编号:

int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void()(void));
该函数用于绑定二进制数据BLOB,其中最后一个参数是一个回调函数,当成功绑定数据后,会被调用,一般用于自动释放对应的缓冲区

int sqlite3_bind_double(sqlite3_stmt*, int, double);
该函数绑定double浮点数

int sqlite3_bind_int(sqlite3_stmt*, int, int);
该函数绑定int整数

int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);
该函数用于绑定具有64位长度的整数,对应于C中的long long结构,由于一个int的范围可能无法满足超大数据量的要求,所以SQLite也支持64位整数,毕竟SQLite官方声称SQLite是支持最大2T的数据的

int sqlite3_bind_null(sqlite3_stmt*, int);
该函数绑定一个空数据到指定列

int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void()(void));
该函数绑定一段字符串,源字符串是UTF8编码的

int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void()(void));
该函数绑定一段字符串,源字符串是UTF16编码的

int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);
该函数绑定以SQLite结构sqlite3_value存储的通用数据,其中sqlite3_value可以是上述的所有类型,此函数不太常用

int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);
该函数绑定指定大小的全零BLOB数据

获取SQL查询结果

对于SELECT语句,还需要能够获取结果。上面也提到调用sqlite3_step之后,对于有结果的查询会返回第一行结果,这时可以通过API函数获取当前行的指定字段结果:

const void sqlite3_column_blob(sqlite3_stmt, int iCol);
该函数以BLOB数据格式获取对应列的数据,BOLB长度使用sqlite3_column_bytes获取

int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
int sqlite3_column_bytes16(sqlite3_stmt*, int iCol);
该函数可以用于返回BLOB和字符串的字节长度。对于BLOB,两个函数效果是一样的,但是对于字符串sqlite3_column_bytes返回的是UTF8编码的字符串长度,而sqlite3_column_bytes16返回的是UTF16编码的字符串长度,其间会做必要的字符串格式转换

double sqlite3_column_double(sqlite3_stmt*, int iCol);
该函数返回double数据列

int sqlite3_column_int(sqlite3_stmt*, int iCol);
该函数返回int数据列

sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol);
该函数返回64位整数,即long long数据

const unsigned char sqlite3_column_text(sqlite3_stmt, int iCol);
const void sqlite3_column_text16(sqlite3_stmt, int iCol);
该函数返回字符串,其中sqlite3_column_text输出的字符串使用UTF8编码

sqlite3_column_text16使用UTF16编码
int sqlite3_column_type(sqlite3_stmt*, int iCol);
该函数返回对应列的数据类型

sqlite3_value sqlite3_column_value(sqlite3_stmt, int iCol);
该函数以sqlite3_value结构体返回数据

上面是根据列ID来获取对应的列数据的,如果想通过列名称获取列数据,则需要将列名称转换为对应的列ID,可以使用下面的函数:
const char sqlite3_column_name(sqlite3_stmt, int N);
const void sqlite3_column_name16(sqlite3_stmt, int N);
该函数返回对应列的名称

关闭数据库

int sqlite3_close(sqlite3 * db);
使用该函数可以关闭数据库

好了,基本的调用流程就这么些了,看完这篇文章,应该能够完成基本的数据读写操作了,至于其他的高级操作,有兴趣的朋友可以登录SQLite官方网站查看,这里就不在本文多说了。

OK,打完收功,欢迎大家多多讨论指正^^^_^