hacker
- 动手实践,查看文档反复循环。动手实践可以把脑海中陌生知识转为熟悉技能。阅读文档可以补充新的学习素材。
- 学会一个 Linux 基本操作(man、grep、vim、输入输出重定向操作符),编辑器工具,要玩的炉火纯青,每个快捷键烂熟于心。
- Desk checking,在编译前检查代码。(code review)
- 出现 bug 时,思考 2 个问题
- 哪些设计或编程习惯导致了这个 bug ?
- 哪些习惯能系统性的预防此类 bug ?
- 项目延期就要延个彻底,预留足够多的时间来处理未完成的事情
- 砍功能也要彻底,不要想着这个功能以后会用。(删除注释掉的代码同理)
- 做工程需要清醒的头脑,不要沉迷于长时间解决某个问题。(10:00 am and 2:00 pm 效率最高)
Merc Release 2.1
Sunday 01 August 1993
Furey mec@shell.portal.com
Hatchet hatchet@uclink.berkeley.edu
Kahn michael@uclink.berkeley.edu
我开 MUD 服务器就是为了学 C 语言编程!
Yeah, right.
本文旨在记录我们的知识、经验和理念。无论您处于什么水平,我们都希望这份文档能帮助您成为更优秀的软件工程师。
请记住,工程实践才是根本,任何文档都无法替代您自己的思考、学习和动手实践。
学习之道
(1) 动手实践
(2) 查阅文档
(3) 继续实践
(4) 再读文档
(5) 深入实践
(6) 反复研读
(7) 持续实践
(8) 温故知新
(9) 明白了吗?
人脑在单次学习中能吸收的新知识量是有限的。动手实践虽然不会带来太多新内容,却能把脑海中的" 陌生知识"转化为"熟悉技能"。阅读文档虽无法让知识变熟悉,却能持续补充新的学习素材。
多数人即便看了文档也很少重温,达到基本够用就止步不前。但现代操作系统、编程语言、网络技术乃至应用软件, 都不可能一蹴而就掌握。必须反复经历"学习-实践"的循环才能真正精通。
环境配置
计算机:运行 Merc 程序的各类主机设备,无论体积大小。每台计算机都有其_制造商_和_型号_标识。以下是您可能会遇到的常见厂商及型号列表:
Manufacturer Model
------------ -----
Sun Sun-2
Sun Sun-3
Sun Sun-4
DEC Vax 5000
DEC Vax 5900
IBM RS/6000
NeXT NextCube
Sequent Symmetry
Sequent Balance
就硬件而言,Merc 可在任何 32 位硬件上运行。
操作系统:计算机上运行的最低层级程序。大多数常见计算机运行 Unix 或其变种版本,如 SunOS
、Ultrix
、AIX
、Mach
或 Dynix
。注意这些变种名称多以 IX
结尾。
Unix 系统主要分为两大"家族":伯克利 Unix(由著名的加州大学伯克利分校开发)和 System 5 Unix(由 Unix 的鼻祖贝尔实验室开发)。
最常见的非 Unix 操作系统是 VMS(DEC 公司为其 VAX 计算机开发的专有系统)。在个人电脑领域,你会看到 IBM PC 及兼容机使用的 MS-DOS 和 OS/2 系统,以及苹果麦金塔电脑的 MacOS 系统。
先说清楚:VAX
不是操作系统,而是 DEC 公司一系列计算机的名称。很多 VAX 机运行 VMS 系统,但运行伯克利 Unix 或 Ultrix 系统的 VAX 机更多。运行 Unix 的 VAX 机与其他运行 Unix 的机器共性更多,反而与运行 VMS 的同系列机型差异更大。
就操作系统兼容性而言,Merc 可在支持伯克利 Unix 版 TCP/IP 网络的 Unix 及其变体系统上运行。它也支持 MS-DOS 系统,但仅限于单用户模式。经过适当修改,Merc 可以移植到任何提供 telnet连接 TCP 服务的操作系统。
编程语言:Merc 采用 C 语言编写。美国国家标准协会(ANSI)制定了 C 语言规范,Merc 完全遵循 ANSI 标准 C 语言规范开发。
最流行的 ANSI 标准 C 编译器是自由软件基金会开发的 GNU gcc
编译器,可通过 prep.ai.mit.edu 匿名 FTP 获取。Merc 在 Gcc 1.38 版本下编译良好,因此您可以直接使用 1.42版本,无需安装更庞大的 2.X 系列。
您并非必须使用 gcc 编译器。运行 AIX 操作系统的 IBM RS/6000 已预装 ANSI C 编译器,NeXT 工作站亦是如此(其标准cc
编译器实际就是 GNU C 编译器)。任何符合 ANSI 标准的编译器都能正常使用。
遗憾的是,目前仍有大量设备未配备 ANSI 标准 C 编译器(Sun工作站在这方面最为典型)。您可以通过mktrad
脚本尝试用非 ANSI(传统)C 编译器编译 Merc,具体操作请参阅 trad.txt 文档。
若不清楚电脑的品牌型号、操作系统以及 C 编译器是 Ansi 还是非 Ansi 标准,您需要先查明这些信息。
Unix 基础工具
man
-- 提供在线手册页查询功能
grep
-- 全称是"全局正则表达式打印"
vi
、emacs
、jove
-- 编辑器随你挑,但必须玩到炉火纯青,每个快捷键都要烂熟于心
ctags
-- 为编辑器生成标签索引,让你能通过函数名直接跳转到源码任意位置
>
、>>
、<
、|
-- 输入输出重定向操作,要么找人演示,要么自己查man csh
手册
这些都是程序员吃饭的家伙什。如果连这些基本功都不扎实,就像开车不会换挡一样寸步难行
调试方法论
调试是门科学。你需要建立假设,基于假设做出预测,运行程序并输入测试数据,观察其行为,最终验证或推翻假设。
优秀的假设能做出令人惊喜且最终应验的预测——这是其他假设无法做到的独特预见。
调试的首要原则是从源头避免制造 bug。这话听着简单,可惜人们总是置若罔闻。
编写程序时若出现任何错误或警告,务必先修正再继续。 C 语言的设计允许许多有缺陷的编码方式合法通过,但足够智能的编译器(比如开启-Wall
选项的 gcc
)会发出警告。花几分钟检查并修复这些警告代码,能为你省下数小时的排错时间。
1993 年时,"Desk checking"(代码审阅)这门手艺几乎失传了,实在可惜。你应当在编译前就进行代码自查,并定期复查以保持思路清晰、发现新问题。如果团队中有人专职审核他人代码,这个人发现的错误会比其他人加起来都多。
人工逐行检查代码每小时能覆盖数百行。顶尖程序员首轮编写的代码准确率约 99%,即每百行仍有一个漏洞。而你并非顶尖选手... 因此通过桌 面检查每小时会发现多个漏洞。这堪称最高效的调试手段——比起在崩溃程序中耗费数小时才能揪出一个错误,效率简直天壤之别。
进阶技巧当属历史悠久的"打印语句调试法"——在代码中插入 print
语句追踪变量值。在 Merc
代码体系中,可调用 printf
或 fprintf
在关键节点输出关键数值。何时何处输出这些数值堪称调试艺术,唯有实践方能掌握其中精髓。
若尚未掌握操作系统中的输出重定向技巧,现在正是学习良机。Unix 系统下输入 man csh
命令,重点研读 >
操作符章节。同时必须厘清标准输出(如 printf
输出)与错误输出(如 fprintf
输出)的本质区别。
归根结底,如果不先理解程序的运行机制,就无法真正修复它。强大的调试工具能帮你收集数据,但它们既不能解读数据,也无法解决根本问题——这些只有你能做到。
发现 bug
时...你的第一反应往往是修改代码、消除表象就宣布修复完成。且慢!你看到的 bug
通常只是更深层问题的症状。应该追根究底,彻底参透这个 bug
的价值后,再让它"魂飞魄散"。
此外,发现 bug
时要问自己两个问题:"最初是哪些设计和编程习惯导致了这个问题?"以及:"哪些系统性习惯能预防此类 bug
的产生?"
调试工具篇
当 Unix 进程访问无效内存地址时,或(较罕见地)执行非法指令,或(更罕见地)发生其他异常时,Unix操作系统会接管控制权。该进程无法继续执行 且必须被终止。但在终止前,系统会执行一项辅助操作:创建一个名为core
的文件,并将该进程的完整数据空间写入其中。
因此,dumping core
并非问题的成因,甚至不是问题的直接表现。这是操作系统为帮助开发者定位导致进程崩溃的致命错误而提供的诊断机制。
开发者需使用调试器分析core
文件。Unix 系统最常用的调试器是adb
和gdb
,偶尔也会遇到dbx
。典型启动方式如:adb merc
或gdb merc core
。
调试时首先要做且往往唯一需要做的就是获取堆栈追踪。在adb
中对应命令是$c
,gdb 中则是backtrace
。
堆栈追踪能显示程序崩溃时所在的函数及其调用链,调试器还会列出这些函数的参数。要正确解析这些参数并运用更高级的调试功能,需要扎实的汇编语言编程功底。
如果你能使用名为Purify
的程序...务必掌握其用法。
性能分析
程序性能分析步骤如下:
(1) Remove all the .o files and the merc
executable:
rm *.o merc
(2) Edit your makefile, and change the PROF= line:
PROF = -p
(3) Remake merc:
make
(4) 照常运行 merc。当运行足够长时间获得良好的性能分析基准后,使用 shutdown 命令关闭游戏。如果游戏崩溃或从外部终止进程,将无法获取性能分析数据。
(5) 运行prof
命令:
prof merc > prof.out
(6) 阅读 prof.out 文件。执行man prof
命令了解输出格式说明。
要进行高级性能分析,可在步骤(2)中使用PROF = -pg
参数,并在步骤 5 执行 gprof
命令。gprof
分析工具能生成详细报告,精确显示函数间的调用次数。
这些数据对调试和性能优化都极具价值。prof
和gprof
工具的可用性因系统而异。几乎所有 Unix 系统都内置 prof
工具,但只有部分系统支持gprof
。
进度、功能与质量的三角博弈
现在简单谈谈项目管理
几乎所有项目最终都会面临进度、功能与质量的取舍困境。就像学生在截止前夜赶论文时,面临三个痛苦选择:要么迟交(牺牲进度),要么缩减内容(阉割功能),要么胡编乱造(降低质量)。
软件开发同样如此,团队常在三种方案间纠结:要么延期发布,要么砍掉功能,要么硬着头皮按时交付——而最后这种情况往往以失败告终。
这个决定最关键的是要意识到它确实是个决定。别指望会有奇迹发生来逃避。如果你不主动应对,外部环境就会替你做出选择。
假设你面临取舍选择了延期,那就别小打小闹——要延就延个痛快。如果想着"解决这个问题就尽快收尾",事后你准会后悔没多留点时间。但如果说"需要多干一天,干脆延期一周",到周末时完成质量反而更有保障。一次性大幅延期,总比重蹈覆辙地每天每小时拖延强。
如果要砍功能,同样要下狠手。别畏首畏尾地幻想"等有空再做"。这个功能已经彻底出局了,抓住需求简化的机会能省则省!
关于如何降低质量我给不出什么建议,这永远 是我在项目中最后才会考虑的妥协方案。
睡眠之道
道理简单却深刻:做工程需要清醒的头脑。
连续数小时埋头解决问题既轻松又诱人。人们会进入一种"心流"状态,满脑子都是问题,工作成果源源不断地涌现。许多作家描述过这种体验——仿佛亲眼目睹故事展开,只需将所见记录下来,就能一页接一页地创作。软件工程师们也常有类似感受,代码似乎自发地从指尖流淌而出。
我相信最有价值的工作往往诞生于这种状态。
但根据我的经验,"心流"状态往往在不知不觉中悄然消退。等我反应过来时,新工作已经不再行云流水般从手中产出,反而要花大量时间修正刚犯的错误。原本灵光闪现的头脑开始被各种疑虑和问题占据。
这时候很容易产生"再熬几小时"的冲动。"反正都在这儿了,刚才效率那么高,干脆通宵搞定算了?"千万别上当!这绝对是个陷阱!
我的建议是:回家吃饭、冲澡、睡觉,让自己满血复活。第二天再战。睡眠时大脑会自动处理问题,醒来时往往就有新思路。第二天上午10 点到下午 2 点的高效产出,绝对胜过熬夜到凌晨的混沌状态。
这个策略有个难点:如何重燃晨间斗志。如果是自主选择的项目还好说,但面对不得不做的任务时,就得权衡"早起没干劲"和"熬夜低效率"的利弊了。