You are on page 1of 65

Writing Solid Code

Zubin Huang

12/09/21 1
Reference
Writing Solid Code, Steve Maguire,
Microsoft Press, 1993, ISBN 1-55615-
551-4

12/09/21 2
Mission
BUGS, BUGS and BUGS …
Who is responsible for the BUGGY
Program
 Find and fixing bugs was DEVELOPER’s
responsibility
Zero defects? Can we success? How?

12/09/21 3
Feasibility
“I don’t expect programmers to write
flawless code every time they sit at the
keyboard, but I do believe that it’s
possible to keep bugs out of the master
sources.”
-- Steve Maguire

12/09/21 4
Every Bug Found
How could I have automatically
detected this bug?
How could I have prevented this bug?

Better Testing? NO!

12/09/21 5
Use tools to find bugs
Enable all optional compiler warnings
Use Tools (Purify, BoundCheck, lint,
mportal) to catch bugs that your
compiler may miss
If you have unit tests, use them

12/09/21 6
Assert Yourself
Undefined vs Steer Clear
 Use assertions to catch illegal uses of
undefined behavior
Get bugs in assertion?
 Should fix the program?
 Fix the assertion?
 Document unclear assertion.

12/09/21 7
Assert Yourself …
/* Check both outputs for validity */
ASSERT( pcRet == pcDisasmAlt(pc, &opc));
ASSERT( compare_opc(popcRet, &opc) ==SAME);

DO NOT disturb the surrounding code in


an unexpected way in ASSERT

12/09/21 8
FORTIFY YOUR SUBSYSTEMS
How are programmers going to
misuse this subsystem, and how
can I detect these problems
automatically

12/09/21 9
There’s No Need to Know
Suppose a new programmer creates a lost
memory block?
 The checks kick in
 New Programmer (NP) not know what a lost
memory block is
 NP not need to know that for the checks to work
 NP can track down the failure
Guideline: Strive to implement transparent
integrity checks

12/09/21 10
You Don’t Ship the Debug
Version
You use the debug version to find bugs
You use the release version to please
customers

12/09/21 11

MS result:
 the debug version of your application –
packed with assertions and subsystem
tests – should run at about half the speed
of your ship version.
Guideline: Don’t apply ship version
constraints to the debug version. Trade
size and speed for error detection.

12/09/21 12
Step Through Your Code

12/09/21 13
Best Way to Find BUG
Execute the code and somehow spot
them
 By EYE
 By using automated tests such as
assertions and subsystem integrity checks
Step though all NEW or MODIFIED code
to watch it execute

12/09/21 14
Gain Confidence in Your Code
RPG
 A went to B office and said: “I think I found a bug in the code you
just finished”
 B load the code into an editor and A show the problem. B
surprised.
 B: “You’re right; the code is definitely wrong. I wonder why my
test cannot catch the bug.”
 A: “How exactly did you test the code?”
 B explained the test to A, and it seems as if it should have caught
the bug.
 A: “Let’s set a breakpoint on the function and step through the
code”
 B set the breakpoint and hit “Run”, the test ran to completion.
NEVER hit the breakpoint!

12/09/21 15
Best Way
The best way to test your code is by
stepping through it and taking a
microscopic look at the intermediate
result.
 Step though every Code path
 Focus on Data flow
 Are you missing something

12/09/21 16
A Fork in Your Code
Step through every instruction of your
code at least once to verify that it works
correctly.
Guideline: Step through every code
path.

12/09/21 17
Data Flow, the Lifeblood of
Code
As you step through code, focus on data flow
 Overflow and underflow bugs
 Data conversion bugs
 Off-by-one bugs
 NULL pointer bugs
 Using garbage memory (0xA3 bugs)
 Assignment bugs in which you’ve used = instead
of ==
 Precedence bugs
 Logic bugs

12/09/21 18
Are You Missing Something
Anytime you step to a compound conditional
that use && and || operators, scan the list to
verify that they are “spelled” correctly.
Use the debugger to display the result for
each side of the expression
Guideline: Source code debuggers can hide
execution details. Step through critical code
at the instruction level.

12/09/21 19
Candy-Machine
Interfaces

12/09/21 20
getchar()?
Two samples:
1. char c;
c = getchar();
if (c == EOF)

2. /*strdup – allocate a duplicate of a string */
char *strdup(char *str)
{
char *strNew;
strNew = (char *)malloc(strlen(str)+1);
strcpy(strNew, str);
return (strNew);
}

12/09/21 21
What’s the problem?
The problem with these functions is not that they
return errors, but that they bury those errors in
normal return values where it’s easy for
programmers to overlook them.
The reality is that combining exclusive outputs into a
single return value is a carryover from assembly
language, where you have a limited number of
machine registers to manipulate and pass data
Guideline: Make it hard to ignore error conditions.
Don’t bury error codes in return values.

12/09/21 22
Read Between The Lines
Examine the interfaces from the point of view of the caller.
Ex:
#define BASE10 1
#define BASE16 0
UnsignedToStr(u, str, BASE10);
UnsignedToStr(u, str, BASE16);
void UnsignedToStr(unsigned u, char *str, flag fDecimal);

void UnsignedToStr(unsigned u, char *str, unsigned base);
Guideline: Make the code intelligible at the point of call; Avoid
boolean arguments.

12/09/21 23
Performance, Memory
Footprint & Risky
If you were to put a programmer at the top of a cliff and give
him/her a rope and a hang glider, how do you think he’d get to
the bottom?
 Climb down the rope
 Glide to the bottom
 Jump to the bottom
Why programmers ignore risk?
 They blindly assume that no matter how they implement their
code, they’re going to implement it without bugs.
 They have never been taught to ask question such as,
 “How risky is this design?”
 “How risky is this implementation?”
 “Is there a safer way to write this expression?”
 “Is it even possible to test this design?”

12/09/21 24
How Long is a long
for(int i=0; i<MAX_RANGE; i++)
{

}

Guideline: Use well-defined data types.


Guideline: Always ask, “Can this variable or
expression over- or underflow”

12/09/21 25
Function Just “Doing
Their Thing”
Don’t accept special purpose arguments
such as the NULL pointer.
Implement your design, not something
that approximates it.
Strive to make every function perform
its task exactly one time.

12/09/21 26
High Risk, No Return
int function(int i) { if (i ==1) return 0;}
int j = function(2);

Guideline: always return.

12/09/21 27
Cost
Using bitwise operators to multiply,
divide, and mod values by a power of
2?
Guideline: Avoid risky language idioms.
Don’t overestimate the cost!

12/09/21 28
Inconsistency
Don’t mix operator types if you don’t
have to.
If you must mix operator types, use
parentheses to isolate those operations.
Don’t look up the precedence, if you
have to, the code is not obvious; make
it obvious.

12/09/21 29
Failures
Don’t call functions that return errors.
If you repeatedly handle the same error
condition throughout your program,
isolate that error handling
How do you handle the error? Do you
alert the user? Ignore the request and
silently keep the old one?

12/09/21 30
Example
if (fResizeMemory(&pwnd->strWndTitle, strlen(strNewTitle)+1))
strcpy(pwn->strWndTitle, strNewTitle);
else
/* Unable to allocate space for the window title … */

void RenameWindow(window *pwnd, char *strNewTitle)


{
ASSERT(fValidWindow(pwnd));
ASSERT(strNewTitle != NULL);
ASSERT(fValidPointer(pwnd->strWndTitle, sizeMaxWndTitle));
strcpy(pwnd->strWndTitle, strNewTitle);
}
12/09/21 31
The Need for Speed
void *memchr(void *pv, unsigned char ch, size_t size)
{
unsigned char *pch = (unsigned char *)pv;

while (size-- > 0)


{
if (*pch == ch)
return (pch);
pch++;
}
return (NULL);
}
12/09/21 32
A “Faster” Version?
void *memchr(void *pv, unsigned char ch, size_t size)
{
unsigned char *pch = (unsigned char *)pv;
unsigned char *pchPlant;
unsigned char chSave;
/* pchPlant points to the first character following the memory run that memchr
is searching. Plant ch at that location so that memchr is guaranteed to find ch
even if it is not in the run.
*/
pchPlant = pch + size;
chSave = *pchPlant; /*save the original char */
*pchPlant = ch;
while (*pch != ch)
pch++;
*pchPlant = chSave; /*Now put the original back */
return ((pch == pchPlant) ? NULL : pch) ;
}

12/09/21 33
The bugs?
This version of memchr has more problems
than Batman has gadgets:
1. pchPlant points to read-only memory
2. pchPlant points to memory-mapped I/O
3. pchPlant points to the last size byte of RAM
4. pchPlant points to data shared by concurrent
processes
Guideline: Don’t reference memory that you
don’t own.

12/09/21 34
Take Only What You
Need
/* UnsToStr – convert an unsigned value to a string */
void UnsToStr(unsigned u, char *str)
{
char *strStart = str;
do
*str++ = (u % 10) + ‘0’;
while (( u /= 10) > 0);
*str = ‘\0’;

Wastful calling ReverseStr()?


ReverseStr(strStart);
}

12/09/21 35
Another version
void UnsToStr(unsigned u, char *str)
{
char *pch;
/* u out of range? Use UlongToStr … */
ASSERT(u <= 65535);
/* Store the digits in str from back to front. Start storing the digits
deep enough into the string to hold the largest possible value for u */
pch = &str[5];
*pch = ‘\0’;
do
*--pch = (u % 10) + ‘0’;
while (( u /= 10 ) > 0 );
strcpy(str, pch);
}

12/09/21 36
How about this version?
void UnsToStr(unsigned u, char *str)
{
char strDigits[6]; /* conversion buffer */
char *pch;
/* u out of range? Use UlongToStr … */
ASSERT(u <= 65535);
/* Store the digits in str from back to front. */
pch = &strDigits[5];
*pch = ‘\0’; •Guideline: Don’t use
do
*--pch = (u % 10) + ‘0’; output memory as
while (( u /= 10 ) > 0 ); workspace buffers
strcpy(str, pch);
}

12/09/21 37
Yet Another Version
char *strFromUns(unsigned u)
{
static char *strDigits = “??????”; /*5 chars + ‘\0’ */
char *pch;
ASSERT( u <= 65535 );
/* store the digits in strDigits from back to front */
pch = &pchDigits[5];
ASSERT(*pch == ‘\0’);
do
*--pch = (u % 10) + ‘0’;
while (( u /= 10 ) > 0) ;
return (pch);
}

12/09/21 38
Keep Private Things
to Yourself
Don’t pass data in global buffers unless
you absolutely have to.
Same rule apply to static memory.
Guideline: Don’t pass data in static (or
global) memory.

12/09/21 39
Functional Leeches
Passing data in public buffers is risky, but you
can get away with it if you’re careful and a bit
lucky. But writing parasite functions that rely
on the internal workings of other function is
not only risky, but also irresponsible: if you
change the host function, you kill the
parasite.
Guideline: Don’t write parasite functions.

12/09/21 40
Example
/* CMOVE – move memory using a head-to-head move */
void CMOVE(byte *pbFrom, byte *pbTo, size_t size)
{
while (size-- > 0)
*pbTo++ = *pbFrom++;
}
/* FILL – fill a range of memory */
void FILL(byte *pb, size_t size, byte b)
{
if (size > 0)
{
*pb = b;
CMOVE(pb, pb+1, size-1);
}
}

12/09/21 41
Size?
There is a correlation between the size
of your C code and the size of the
corresponding machine code, but that
correlation breaks down when you
apply it to individual lines of code.
Guideline: Tight C does not guarantee
efficient machine code.

12/09/21 42
No Hoity-Toity
Programming
void *memmove(void *pvTo, void *pvFrom, size_t size)
{ byte *pbTo = (byte *)pvTo;
byte *pbFrom = (byte *)pvFrom;
((pbTo>pbFrom)?tailmove:headmove)(pbTo, pbFrom, size);
return (pvTo);
}
void *memmove(void *pvTo, void *pvFrom, size_t size)
{ byte *pbTo = (byte *)pvTo;
byte *pbFrom = (byte *)pvFrom; •Guideline: Write
if (pbTo>pbFrom) code for the “average”
tailmove(pbTo, pbFrom, size);
else
programmer
headmove(pbTo, pbFrom, size);
}
12/09/21 43
The Rest Is Attitude

12/09/21 44
For My Next Trick,
Disappearing Bugs
3 reasons bugs disappear:
1. The bug report was wrong
2. The bug has been fixed by another programmer
3. The bug still exists but isn’t apparent
Professional programmer: it is your job to
determine which of the 3 cases described
my disappearing bug and to act accordingly.
Guideline: Bugs don’t just “go away”.

12/09/21 45
Save Time
You don’t save time by fixing bugs late in the product cycle. In
fact, you lose time because it’s often harder to fix bugs in code
you wrote a year ago than you wrote days ago.
Fixing bugs “as you go” provides damage control because the
earlier you learn of your mistakes, the less likely you are to
repeat them.
Bugs are a form of negative feedback that keep fast but sloppy
programmers in check.
By keeping the bug count near zero, you have a much easier
time predicting when you’ll finish the product.
Guideline: Don’t fix bugs later, fix them now.

12/09/21 46
Bug-Doctor to the
Rescue!
Like a doctor, programmers are sometimes so
busy “healing” bugs that they never stop to
figure out what’s causing them.

if (pb == NULL)
return (FALSE);

Guideline: Fix the cause, not the symptom.

12/09/21 47
Are You a Code
Meddler?
char chGetNext(void)
{
int ch; /*ch *must* be an int */
ch = getchar();
return (chRemapChar(ch));
}
char chGetNext(void)
{
return (chRemapChar(getchar());
}
Guideline: Don’t clean up code unless the clean-up is
critical to the product’s success.

12/09/21 48
“Cool” Feature Cool?
Don’t write (or change) code if you
don’t have to.
Put “cool” features into cold storage.
Guideline: Don’t implement non-
strategic features.

12/09/21 49
Free Lunch?
“Free” features are another source of unnecessary
bugs.
 They fall out of existing designs with little or no effort.
 Almost never critical to the success of the product.
Fallacy: free features may not cost the programmer
much, but there is more to a feature than code.
Somebody has to write document for the feature.
Somebody has to test the feature. And of course
somebody has to fix any bugs that show up with the
feature.
Guideline: There are no free lunch.

12/09/21 50
Flexibility Breeds
Bugs
Another strategy to prevent bug is to strip
unnecessary flexibility from your design.
When you implement features in your own
projects, make them easy to use; don’t make
them unnecessary flexible. There is a
difference.
Guideline: Don’t allow unnecessary flexibility.

12/09/21 51
The Sacred Schedule
Facing a sizable feature:
 Some will spend two weeks hunched over a
keyboard writing code, never bothering to test
their work.
 Some will implement a dozen small features
before stopping to check things out.
Guideline: Write and test code in small
chunks. Always test your code, even if that
means your schedule will slip.

12/09/21 52
Testers Wear White
Hats
When a tester reports a bug in your code,
your first reaction should be shock and
disbelief – you should never expect testers to
find bugs in your code. Your second reaction
should be gratitude because that tester has
saved you from shipping a bug.
There are no silly bugs. A bug may be minor,
that it exists is serious.
Guideline: Don’t blame testers for finding
your bugs.

12/09/21 53
What’s Next?
Use the source code control manager to
list my changes before merging them
into the master sources.
Keep bugs out of your code is a priority.
If you’re not using unit tests, maybe
you should start.
Add DEBUG code specifically to help
your testers.

12/09/21 54
NEVER ALLOW THE
SAME BUG TO BITE
YOU TWICE.

12/09/21 55
Coding Checklist

12/09/21 56
一般问题
你是否为程序建立了 DEBUG 版本
你是否将发现的错误及时改正了
你是否坚持彻底测试代码,即使耽误了进程也在所不

你是否依靠测试组为你测试代码
你是否知道编码的优先顺序
你的编译程序是否有可选的各种警告

12/09/21 57
关于将更改归并到主程序
你是否将编译程序的警告 ( 包括可选的 ) 都处理了
你的代码是否未用 LINT
你的代码进行了单元测试吗
你是否逐步通过了每一条编码路径以观察数据流
你是否逐步通过了汇编语言层次上的所有关键代码
是否清理过了任何代码?若是,修改处经过彻底测试了吗
文档是否指出了使用你的代码有危险之处
程序维护人员是否能够理解你的代码

12/09/21 58
每当实现了一个函数或子系统之时

是否用断言证实了函数参数的有效性
代码中是否有未定义的或无意义的代码
代码能否创建未定义的数据
有没有难以理解的断言?对它们作了解释没有
你的代码中是否作过任何假设
是否使用断言警告可能出现的非常情况
是否作过防御性程序设计?代码是否隐藏了错误
是否用第二个算法来验证第一个算法
是否有可用于确认代码或数据的启动 (startup) 检查

12/09/21 59
每当实现了一个函数或子系统之时

代码是否包含了随机行为?能消除这些行为吗
你的代码若产生了无用信息,你是否在 DEBUG 代码中把它们
置为无用信息
代码中是否有稀奇古怪的行为
若代码是子系统的一部分,那么你是否建立了一个子系统测

在你的设计和代码中是否有任意情况
即使程序员不感到需要,你也作完整性检查吗
你是否因为排错程序太大或太慢,而将有价值得 DEBUG 测试
抛置一边
是否使用了不可移植的数据类型

12/09/21 60
每当实现了一个函数或子系统之时
代码中是否有表达式或变量产生上溢或下溢
是否准确地实现了你的设计?还是非常近似地实现了
你的设计
代码是否不止一次地解同一个问题
是否企图消除代码中的每一个 IF 语句
是否用过嵌套?:运算符
是否已将专用代码孤立出来
是否用到了有风险的语言惯用语
是否不必要地将不同类型的运算符混用
是否调用了返回错误的函数?你能消除这种调用吗

12/09/21 61
每当实现了一个函数或子系统之时
是否引用了尚未分配的内存空间
是否引用了已经释放了的内存空间
是否不必要地多用了输出缓冲存储
是否向静态或全局缓冲区传送了数据
你的函数是否依赖于另一个函数的内部细节
是否使用了怪异的或有疑问的 C 惯用语
在代码中是否有挤在一行的毛病
代码中有不必要得灵活性吗?你能消除它们吗
你的代码是经过多次“试着”求解的结果吗
函数是否小并容易测试

12/09/21 62
每当设计了一个函数或子系统之后

此特征是否符合产品的市场策略
错误代码是否作为正常返回值的特殊情况而隐藏起来
是否评审了你的界面,它能保证难于出现误操作吗
是否具有多用途且面面俱到的函数
你是否有太灵活的 ( 空空洞洞的 ) 函数参数
当你的函数不再需要时,它是否返回一个错误条件
在调用点你的函数是否易读
你的函数是否有布尔量输入

12/09/21 63
修改错误之时

错误无法消失,是否能找到错误的根源
是修改了错误的真正根源,还是仅仅修改了错误的症

12/09/21 64
Q&A

12/09/21 65

You might also like