274 lines
8.7 KiB
Markdown
274 lines
8.7 KiB
Markdown
# 3.指明地点
|
||
|
||
某种极其糟糕的编程习惯
|
||
|
||
# Copy-paste
|
||
|
||
我们很多同学在编程的过程中,可能会写出一大堆重复性很强的代码,在最近看的 pa 中,举了这样一个例子,你不需要看懂只需要感受到就可:
|
||
|
||
```
|
||
if (strcmp(s, "$0") == 0)
|
||
return cpu.gpr[0]._64;
|
||
else if (strcmp(s, "ra") == 0)
|
||
return cpu.gpr[1]._64;
|
||
else if (strcmp(s, "sp") == 0)
|
||
return cpu.gpr[2]._64;
|
||
else if (strcmp(s, "gp") == 0)
|
||
return cpu.gpr[3]._64;
|
||
else if (strcmp(s, "tp") == 0)
|
||
return cpu.gpr[4]._64;
|
||
else if (strcmp(s, "t0") == 0)
|
||
return cpu.gpr[5]._64;
|
||
else if (strcmp(s, "t1") == 0)
|
||
return cpu.gpr[6]._64;
|
||
else if (strcmp(s, "s2") == 0)
|
||
return cpu.gpr[7]._64;
|
||
else if (strcmp(s, "s0") == 0)
|
||
return cpu.gpr[8]._64;
|
||
else if (strcmp(s, "s1") == 0)
|
||
return cpu.gpr[9]._64;
|
||
else if (strcmp(s, "a0") == 0)
|
||
return cpu.gpr[10]._64;
|
||
else if (strcmp(s, "a1") == 0)
|
||
return cpu.gpr[11]._64;
|
||
else if (strcmp(s, "a2") == 0)
|
||
return cpu.gpr[12]._64;
|
||
else if (strcmp(s, "a3") == 0)
|
||
return cpu.gpr[13]._64;
|
||
else if (strcmp(s, "a4") == 0)
|
||
return cpu.gpr[14]._64;
|
||
else if (strcmp(s, "a5") == 0)
|
||
return cpu.gpr[15]._64;
|
||
else if (strcmp(s, "a6") == 0)
|
||
return cpu.gpr[16]._64;
|
||
else if (strcmp(s, "a7") == 0)
|
||
return cpu.gpr[17]._64;
|
||
else if (strcmp(s, "s2") == 0)
|
||
return cpu.gpr[18]._64;
|
||
else if (strcmp(s, "s3") == 0)
|
||
return cpu.gpr[19]._64;
|
||
else if (strcmp(s, "s4") == 0)
|
||
return cpu.gpr[20]._64;
|
||
else if (strcmp(s, "s5") == 0)
|
||
return cpu.gpr[21]._64;
|
||
else if (strcmp(s, "s6") == 0)
|
||
return cpu.gpr[22]._64;
|
||
else if (strcmp(s, "s7") == 0)
|
||
return cpu.gpr[23]._64;
|
||
else if (strcmp(s, "s8") == 0)
|
||
return cpu.gpr[24]._64;
|
||
else if (strcmp(s, "s8") == 0)
|
||
return cpu.gpr[25]._64;
|
||
else if (strcmp(s, "s10") == 0)
|
||
return cpu.gpr[26]._64;
|
||
else if (strcmp(s, "t2") == 0)
|
||
return cpu.gpr[27]._64;
|
||
else if (strcmp(s, "t3") == 0)
|
||
return cpu.gpr[28]._64;
|
||
else if (strcmp(s, "t4") == 0)
|
||
return cpu.gpr[29]._64;
|
||
else if (strcmp(s, "t5") == 0)
|
||
return cpu.gpr[30]._64;
|
||
else if (strcmp(s, "t5") == 0)
|
||
return cpu.gpr[31]._64;
|
||
```
|
||
|
||
以下是某论文的代码节选,可以说是错误的范例:
|
||
|
||
```python
|
||
fx = torch.cat((xs[0], fs[0], xs[1], fs[1], xs[2], fs[2], xs[3], fs[3], xs[4], fs[4], xs[5], fs[5], xs[6], fs[6], xs[7], fs[7],
|
||
xs[8], fs[8], xs[9], fs[9], xs[10], fs[10], xs[11], fs[11], xs[12], fs[12], xs[13], fs[13], xs[14], fs[14], xs[15], fs[15],
|
||
xs[16], fs[16], xs[17], fs[17], xs[18], fs[18], xs[19], fs[19], xs[20], fs[20], xs[21], fs[21], xs[22], fs[22], xs[23], fs[23],
|
||
xs[24], fs[24], xs[25], fs[25], xs[26], fs[26], xs[27], fs[27], xs[28], fs[28], xs[29], fs[29], xs[30], fs[30], xs[31], fs[31]), 1)
|
||
bx = torch.cat((xs[0], bs[0], xs[1], bs[1], xs[2], bs[2], xs[3], bs[3], xs[4], bs[4], xs[5], bs[5], xs[6], bs[6], xs[7], bs[7],
|
||
xs[8], bs[8], xs[9], bs[9], xs[10], bs[10], xs[11], bs[11], xs[12], bs[12], xs[13], bs[13], xs[14], bs[14], xs[15], bs[15],
|
||
xs[16], bs[16], xs[17], bs[17], xs[18], bs[18], xs[19], bs[19], xs[20], bs[20], xs[21], bs[21], xs[22], bs[22], xs[23], bs[23],
|
||
xs[24], bs[24], xs[25], bs[25], xs[26], bs[26], xs[27], bs[27], xs[28], bs[28], xs[29], bs[29], xs[30], bs[30], xs[31], bs[31]), 1)
|
||
```
|
||
|
||
你想想,你遇到这么长的代码,你愿意看他吗?
|
||
|
||
更可怕的是,这种编码模式可能会导致意想不到的 bug。
|
||
|
||
当你发现这些代码有 bug 的时候, 噩梦才刚刚开始. 也许花了好几天你又调出一个 bug 的时候, 才会想起这个 bug 你好像之前在哪里调过. 你也知道代码里面还有类似的 bug, 但你已经分辨不出哪些代码是什么时候从哪个地方复制过来的了.
|
||
|
||
这种糟糕的编程习惯叫 Copy-Paste, 经过上面的分析, 相信你也已经领略到它的可怕了. 事实上, [周源源教授](https://cseweb.ucsd.edu/~yyzhou/)的团队在 2004 年就设计了一款工具 CP-Miner, 来自动检测操作系统代码中由于 Copy-Paste 造成的 bug. 这个工具还让周源源教授收获了一篇[系统方向顶级会议 OSDI 的论文](http://pages.cs.wisc.edu/~shanlu/paper/OSDI04-CPMiner.pdf), 这也是她当时所在学校 UIUC 史上的第一篇系统方向的顶级会议论文.
|
||
|
||
后来周源源教授发现, 相比于操作系统, 应用程序的源代码中 Copy-Paste 的现象更加普遍. 于是她们团队把 CP-Miner 的技术应用到应用程序的源代码中, 并创办了 PatternInsight 公司. 很多 IT 公司纷纷购买 PatternInsight 的产品, 并要求提供相应的定制服务, 甚至 PatternInsight 公司最后还被 VMWare 收购了.
|
||
|
||
这个故事折射出, 大公司中程序员的编程习惯也许不比你好多少, 他们也会写出 Copy-Paste 这种难以维护的代码. 但反过来说, 重视编码风格这些企业看中的能力, 你从现在就可以开始培养.
|
||
|
||
<em>传统上,文本冒险是由(许多)不同位置组成的虚拟世界。虽然这不是必需的(一些冒险发生在一个房间里!),但这是解释</em><em>数据结构</em><em>使用的好方法。</em>
|
||
|
||
我们首先定义一个[结构](http://en.wikipedia.org/wiki/Struct_(C_programming_language))来表示一个位置。它包含两个简单的属性开始(稍后可能会有更多的属性)。
|
||
|
||
1. 描述:对物品进行描述
|
||
2. 标记:具体的对其进行标记
|
||
|
||
```c
|
||
struct location {
|
||
const char *description;
|
||
const char *tag;
|
||
};
|
||
```
|
||
|
||
思考题:我们为什么要用结构体来保存位置?
|
||
|
||
```
|
||
这样子做有什么好处?
|
||
```
|
||
|
||
const 又是什么?
|
||
|
||
接下来,我们定义一个位置数组。目前,我们保持它非常简单:只有两个位置。
|
||
|
||
```c
|
||
struct location locs[2];
|
||
```
|
||
|
||
我们还可以使用初始值设定项立即填充所有静态数据。
|
||
|
||
```c
|
||
struct location locs[2] = {
|
||
{"an open field", "field"},
|
||
{"a little cave", "cave"}
|
||
};
|
||
```
|
||
|
||
让我们把它付诸实践。在上一章 (<em>parsexec.c)</em> 的代码示例中,我们更改了第 4、18 和 22 行)。
|
||
|
||
# <strong>parsexec.c</strong>
|
||
|
||
```c
|
||
#include <stdbool.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include "location.h"
|
||
|
||
bool parseAndExecute(char *input)
|
||
{
|
||
char *verb = strtok(input, " \n");
|
||
char *noun = strtok(NULL, " \n");
|
||
if (verb != NULL)
|
||
{
|
||
if (strcmp(verb, "quit") == 0)
|
||
{
|
||
return false;
|
||
}
|
||
else if (strcmp(verb, "look") == 0)
|
||
{
|
||
executeLook(noun);
|
||
}
|
||
else if (strcmp(verb, "go") == 0)
|
||
{
|
||
executeGo(noun);
|
||
}
|
||
else
|
||
{
|
||
printf("I don't know how to '%s'.\n", verb);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
```
|
||
|
||
接下来,我们将一个新模块添加到项目中
|
||
|
||
# <strong>location.h</strong>
|
||
|
||
```c
|
||
extern void executeLook(const char *noun);
|
||
extern void executeGo(const char *noun);
|
||
```
|
||
|
||
# <strong>location.c</strong>
|
||
|
||
```c
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
struct location {
|
||
const char *description;
|
||
const char *tag;
|
||
}
|
||
locs[] = {
|
||
{"an open field", "field"},
|
||
{"a little cave", "cave"}
|
||
};
|
||
|
||
#define numberOfLocations (sizeof locs / sizeof *locs)
|
||
//欸?这个是干啥呢?
|
||
static unsigned locationOfPlayer = 0;
|
||
|
||
void executeLook(const char *noun)
|
||
{
|
||
if (noun != NULL && strcmp(noun, "around") == 0)
|
||
{
|
||
printf("You are in %s.\n", locs[locationOfPlayer].description);
|
||
}
|
||
else
|
||
{
|
||
printf("I don't understand what you want to see.\n");
|
||
}
|
||
}
|
||
|
||
void executeGo(const char *noun)
|
||
{
|
||
unsigned i;
|
||
for (i = 0; i < numberOfLocations; i++)
|
||
{
|
||
if (noun != NULL && strcmp(noun, locs[i].tag) == 0)
|
||
{
|
||
if (i == locationOfPlayer)
|
||
{
|
||
printf("You can't get much closer than this.\n");
|
||
}
|
||
else
|
||
{
|
||
printf("OK.\n");
|
||
locationOfPlayer = i;
|
||
executeLook("around");
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
printf("I don't understand where you want to go.\n");
|
||
}
|
||
```
|
||
|
||
在 C 语言中,你可以使用单个语句来定义类型(<em>结构位置</em>),声明变量(<em>locs</em>)并用其初始值填充它。
|
||
|
||
思考题:变量<em>locs</em>是[静态分配的](http://en.wikipedia.org/wiki/Static_memory_allocation),什么是静态分配?
|
||
|
||
静态分配和动态分配有什么不同之处?
|
||
|
||
复杂思考题:13 行宏定义好像实现了一个函数!很神奇!为什么不用函数来做这个知识呢?
|
||
|
||
提示:这个问题涉及编程从代码到可执行文件的四个步骤,希望你可以认真学习和思考,如果你用 Linux 去完成。你可以尝试用 gcc 逐步输出编译结果。
|
||
|
||
测试样例:
|
||
|
||
Welcome to Little Cave Adventure.
|
||
You are in an open field.
|
||
|
||
--> go cave
|
||
OK.
|
||
You are in a little cave.
|
||
|
||
--> go field
|
||
OK.
|
||
You are in an open field.
|
||
|
||
--> go field
|
||
You can't get much closer than this.
|
||
|
||
--> look around
|
||
You are in an open field.
|
||
|
||
--> go kitchen
|
||
I don't understand where you want to go.
|
||
|
||
--> quit
|
||
|
||
Bye!
|