代码工程

通用的代码工程经验:

怎么让代码写得更加可读?对仗工整、减少逻辑嵌套。

怎么让代码写得更加健壮?防御性编程。

怎么让代码写得性能更好?特定技巧。

可读性

if-else 避免嵌套

1
2
3
4
5
6
7
public int findK(int[] nums, int l, int r, int k){
if(l>=r && l!=k) return -1;
int pos = partition(nums, l, r);
if(pos < k) return findK(nums, pos+1, r, k);
if(pos > k) return findK(nums, l, pos-1, k);
return nums[pos];
}

注意看其中的if语句,虽然可以整合为if else语句,但这样会丢失判断条件的可见性(多一层逻辑思考),可读性下降。

并且,这么写,代码也工整对仗美观。

健壮性

防御性编程

https://swc-osg-workshop.github.io/2017-05-17-JLAB/novice/python/05-defensive.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
int exec(char *path, char **argv)
{
char *s, *last;
int i, off;
uint64 argc, sz = 0, sp, ustack[MAXARG], stackbase;
struct elfhdr elf;
struct inode *ip;
struct proghdr ph;
pagetable_t pagetable = 0, oldpagetable;
struct proc *p = myproc();

begin_op(); // redo日志

if((ip = namei(path)) == 0){ // Look up and return the inode for a path name.
end_op();
return -1;
}
ilock(ip);

// Check ELF header
if(readi(ip, 0, (uint64)&elf, 0, sizeof(elf)) != sizeof(elf))
goto bad;
if(elf.magic != ELF_MAGIC)
goto bad;

if((pagetable = proc_pagetable(p)) == 0) // Create a user page table for a given process,
goto bad;

// Load program into memory.
for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){
if(readi(ip, 0, (uint64)&ph, off, sizeof(ph)) != sizeof(ph))
goto bad;
if(ph.type != ELF_PROG_LOAD)
continue;
if(ph.memsz < ph.filesz)
goto bad;
if(ph.vaddr + ph.memsz < ph.vaddr) //checks for whether the sum overflows a 64-bit integer
goto bad;
uint64 sz1;
if((sz1 = uvmalloc(pagetable, sz, ph.vaddr + ph.memsz)) == 0) //oldsz newsz
goto bad;
sz = sz1;
if((ph.vaddr % PGSIZE) != 0)
goto bad;
if(loadseg(pagetable, ph.vaddr, ip, ph.off, ph.filesz) < 0)
goto bad;
}
iunlockput(ip);
end_op();
ip = 0;

p = myproc();
uint64 oldsz = p->sz;

// Allocate two pages at the next page boundary.
// Use the second as the user stack.
sz = PGROUNDUP(sz);
uint64 sz1;
if((sz1 = uvmalloc(pagetable, sz, sz + 2*PGSIZE)) == 0)
goto bad;
sz = sz1;
uvmclear(pagetable, sz-2*PGSIZE);
sp = sz;
stackbase = sp - PGSIZE;

// Push argument strings, prepare rest of stack in ustack.
for(argc = 0; argv[argc]; argc++) {
if(argc >= MAXARG)
goto bad;
sp -= strlen(argv[argc]) + 1;
sp -= sp % 16; // riscv sp must be 16-byte aligned
if(sp < stackbase)
goto bad;
if(copyout(pagetable, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
goto bad;
ustack[argc] = sp;
}
ustack[argc] = 0;

// push the array of argv[] pointers.
sp -= (argc+1) * sizeof(uint64);
sp -= sp % 16;
if(sp < stackbase)
goto bad;
if(copyout(pagetable, sp, (char *)ustack, (argc+1)*sizeof(uint64)) < 0)
goto bad;

// arguments to user main(argc, argv)
// argc is returned via the system call return
// value, which goes in a0.
p->trapframe->a1 = sp;

// Save program name for debugging.
for(last=s=path; *s; s++)
if(*s == '/')
last = s+1;
safestrcpy(p->name, last, sizeof(p->name));

// Commit to the user image.
oldpagetable = p->pagetable;
p->pagetable = pagetable;
p->sz = sz;
p->trapframe->epc = elf.entry; // initial program counter = main
p->trapframe->sp = sp; // initial stack pointer
proc_freepagetable(oldpagetable, oldsz);

return argc; // this ends up in a0, the first argument to main(argc, argv)

bad:
if(pagetable)
proc_freepagetable(pagetable, sz);
if(ip){
iunlockput(ip);
end_op();
}
return -1;
}

任何一环节出错,直接go to bad,提前返回。

接口模块可知假设

在一次需求中,一个内部接口被要求开放给外部,这个需求带来了种种bug。

内部接口要求的数据,并不完全体现在接口参数中,一些数据需要提前计算好,这就带来了灾难。因为人们总是假设接口需要的数据全都体现在接口参数中。内部接口由于不对外,开发者可以从上到下获悉每个步骤所需数据 ,即便耦合一些,也是在内部处理。但是一旦对外,就必须做好这个接口/模块所需全部数据的准备,这个准备从哪得知呢?就是接口参数。

为一个接口准备数据是当然的,问题是怎么知道要准备哪些数据?内部接口我们可以浏览其实现,获取依赖数据部分,但是对外接口相当于黑盒,接口所需数据只能从其参数中获取。

这里想说的仍然是可读性,如果接口参数描述良好,而不是传入一个类指针,这个类包含大量成员,但是接口只使用部分成员,分散的数据容易引发模糊,进而导致失败。

shell相关

set -u

遇到未声明变量直接退出。

这个命令有多好用呢?在编写shell脚本时,shell对于未声明的变量是不会报错的,而是一个空初始值,这就导致程序发生了难以排查的异常。

比如前后依赖项目倒置:

1
2
3
export CODE_BASE=/data/db-server
export LD_LIBRARY_PATH=$MY_HOME/lib # 错误,MY_HOMW未声明!
export MY_HOME=$CODE_BASE/dest/

环境变量可见性

错误复现:shell窗口多开,欲提升效率。在窗口1上修改环境变量source ~/.bashrc 后以为大捷,遂直接在窗口2上编译。

原因分析:多个shell窗口,虽然是同一个用户,但环境变量并不相同。

作者

Desirer

发布于

2026-05-12

更新于

2026-05-12

许可协议