Posted on:
Last modified:
TODO: 总结
作为技术员工,你的一部分工作就是培训周围的非技术人员,讲解开发过程。
在软件开发过程中,如果需求被污染了,那么他就会污染架构,而架构又会污染构建。这样会导致 程序员脾气暴躁、营养失调;开发出的程序具有放射性污染,而且周身都是缺陷。
发现错误的时间要尽可能接近引入错误的时间。
问题定义应该用客户的语言来书写,而且应该从客户的角度来描述问题。最好的解决方案未必是一个计算机程序。
明确的需求免得你去猜测用户想要的是什么。开发过程能够帮助用户更好地理解自己的需求,这是需求变更的主要来源。平均下来,开发过程中有 25% 的需求变化会导致返工量的 75% 以上。
优秀的架构往往适合机器和语言无关的。
没啥可记录的
软件开发最大的挑战还是将问题概念化,编程中的很多错误都是概念性的错误。
应该先做出一个尽可能简单、但能运行的版本。一点点在其上附上肌肉和皮肤,一次增加一部分代码,直到得到一个可以完全工作的系统。
软件的首要技术使命——管理复杂度
理想的设计特征
对子系统之间的通信应该加以限制,越少越好。尤其不要有环向的依赖。
类应该像冰山:八分之七都是位于水面之下,而你能看到的只是水面之上的八分之一。
蛮力也是一种强大的方法。画图是另一种强大的启发式方法。图能在另一个更高的抽象层次上表达问题。不要卡在单一问题上,可以去散散步。
实际开发中的问题和学校作业的区别是,实际开发中的问题可能经常在你提交代码之后需求已经变了,需要再次开发
不懂 ADT 的程序员开发出来的类只是名义上的“类”而已——实际上这种“类”只不过是把一些稍微有点关系的数据和子程序堆在一起。
YN: 使用 ADT 隐藏实现细节,但是也可能是过早优化,比如 JAVA 中讨厌的 getter、setter
使用类的好处是,你可以像在现实世界中
隐式变量声明是一项非常危险的行动。
BAD
account_no = xxx
account_number = 123456 // 记错变量名了
target_account = account_no
GOOD
Python 中无法强制声明变量,可以使用 pylint 等工具
可以通过以下几个方面改善
在需要第一次使用变量的地方声明变量,在变量声明的时候初始化,并且尽量声明为 const 或者 final。
尤其要注意 i,j 这些变量在再次使用的时候有没有再次初始化。
对于 C++ 等语言,要在构造函数中初始化所有变量,并且在析构函数中释放内存。
介于统一变量多个引用点之间的代码称为“攻击窗口”。跨度指的是两次访问同一个变量的间隔,生存时间指的是变量的第一次使用和最后一次使用的间隔。应该尽量减少变量的存活时间和跨度,比如说全局变量的跨度和生存时间都很长,所以他们不好。
在程序中加入断言来验证关键变量的合理取值 在需要删除变量的时候赋值为不合法数值,比如 null
越晚越好
titleBar.color = 0xfff
titleBar.color = WHITE
titleBar.color =
语句一般也分为有副作用的语句和没有副作用的语句,如果前后两个语句都在更新同一个变量,那这样是不好的。至少我们要减少有副作用的语句。这样可以使代码的依赖变得更加明显。代码是按照顺序执行的,对于后一句依赖前一句的代码
比如:
BAD:
ComputeMarketingExpense()
ComputeSaleExpense()
ComouteTravelExpense()
ComputePersonalExpense()
DispalyExpenseSummary()
Good:
market = ComputeMarkingExpense()
sale = ComputeSaleExpense()
travle = ComputeTravelExpense()
personal = ComputePersonalExpense()
Dispaly(ExpenseSummery(market, sale, travle, personal)
对于没有依赖关系的代码,应该让他们尽可能分组,方便自上而下的阅读。最好做到相关代码构成的语句块之间不要有重叠
BAD:
MarketingData marketingData;
SalesData salesData;
TravelData travelData;
travelData.ComputeDuarerly()
salesData.ComputeQuarterly()
maketingData.ComputeQuarterly()
salesData.Print()
travelData.Print()
marketingData.Print()
Good:
MarketingData marketingData;
marketingData.ComputeQuarterly()
marketingData.Print()
TravelData travelData;
travelData.ComputeQuarterly()
travelData.Print()
SalesData salesData
salesData.ComputeQuerterly()
salesData.Print()
书中写到应该优先处理正常情况,但是这样的话可能造成箭头形的代码,个人认为还是有限处理错误并返回比较好。不过如果是正常情况的分支的话,应该优先处理常见情况。对于 switch 语句,应该在 default 字句中处理错误
对于大长串的 and 判断条件建议写一个 is_XXX 函数来封装一下。
循环的种类:
研究显示下面这种代码比传统的 while 或者 do-while 代码更容易理解,以为如果把退出条件强行放到开始或者结尾,那么很难避免重复代码。
while (true) {
// ...
if (xxx) break
// ...
}
像是 for 和 foreach 循环最好用在简单的控制条件,不要在循环体内做改动下标等操作。
对于可能无法终止的循环,可以考虑添加一个安全计数器来保证不会出现死循环。
BAD:
do {
node = node->Next;
} while (node-Next != NULL);
Good:
safeCounter = 0;
do {
node = node->Next;
...
safeCounter++;
if (safeCounter >= SATETY_LIMITE) {
Assert(false, "Internal Error: safe counter violation")
}
...
} while (node->Next != NULL);
continue 语句就相当于一个 else 语句,和之前所述一样,尽量在循环开始的地方使用 continue 语句。对于 return 语句也是一样的。
需要特别注意的是 switch 中也允许 break 语句,所以很可能导致潜在的 bug
// BAD:
network code()
{
switch(line)
{
case THING1:
doit1();
break;
case THING2:
if(x==STUFF)
{
do_first_stuff();
if(y==OTHER_STUFF)
break;
do_later_stuff();
}
/*代码的意图是跳转到这里… …*/
initialize_modes_pointer();
break;
default :
processing();
}
/*… …但事实上跳到了这里。*/
use_modes_pointer(); /*致使 modes_pointer 指针未初始化*/
}
把循环控制在 3 级以内,否则人类就不能理解。把长循环的内容移到子程序中。
可以由内而外的构建循环,也就是说先写出循环体要处理的内容,再去构建循环条件。
递归,首先要写终止语句,并且要尽量避免迭代次数过多的递归,因为每次递归都会产生一个调用栈,可以使用安全计数器来避免无限递归
表驱动法把复杂的条件分支语句转化成通过查表语句。表驱动法还有一个好处就是可以把解析的逻辑放到外部文件中。表驱动的两个问题:
BAD:
if month == 1:
days = 31
elif month == 2:
days = 28
elif month == 3:
days = 31
...
else month == 12:
days = 31
GOOD:
days_per_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
days = days_per_month[month -1]
比如根据分数来确定学生的评级
range_limit = [50.0, 65.0, 75.0, 90.0, 100.0]
grades = ['F', 'D', 'C', 'B', 'A']
max_grade_level = len(grade) - 1
grade_level = 0
student_grade = 'A'
while student_grade == 'A' and grade_level < max_grade_level:
if student_score < range_limit[grade_level]:
student_grade = grades[grade_level]
grade_level += 1
if 语句
对于复杂的 if 语句来说,也可以考虑使用决策表来简化操作。
对于 if 语句中的判断,按照书中的顺序来编写数值表达式:if 1 < a and a < 3
度量复杂度的 Tom McCabe 方法,根据决策点来测试:
0-5, 函数还不错 6-10,需要改进 10+,必须改进
没啥可记得
没啥可记得
推荐的测试方法
目标是覆盖每一个路径,采用的方法和之前计算代码复杂度的方法一样。
如果你发现自己每天多次键入某个长度超过 5 个字母的命令,那么应该用一个脚本
没啥值得记录的
IBM 的研究显示:平均每十行一个注释的代码可读性最高。
代码注释应该着眼于 why 而不是 how
BAD
// if acccount flag is zero
if (accountFlag == 0) ...
GOOD
// if establishing a new acccount
if (accountFlag == 0) ...
BETTER
if (account.type == AccountType.NewAccount) ...
对于非常规的行为要给与注释
你越是谦虚,进步就越快。
如果分配给你的工作净是些不能提高自身技能的短期任务,你理应表示不满。如果正处于竞争激烈的软件市场,则目前工作用到的一半的知识将在三年后过期。假如不持续学习,你就会落伍。
如果在工作中学不到什么,就找一份新的工作吧。
如果不了解所用语言的某一特性是怎么回事,可编写一个小程序来检验,看看它是如何工作的。请在调试器中观察程序的执行情况。用一个小程序来检验某一概念,总比编写大程序时运用不太了解的特性要好。
如果小程序表现的特性与你设想的不一样,怎么办呢?那正是你要研究的问题。最好通过小程序找出答案,而不要用大程序。有效编程的关键之一就是要学会迅速制造错误,并且每次都能从中有所收获。犯错不是罪过,从中学不到什么才是罪过。
阅读解决问题的有关方法。解决问题是软件创作过程中的核心行为。就算你想再发明个车轮,也不会注定成功,你发明的也许是方车轮。
在行动之前作出分析和计划。在分析和行动直接有着矛盾关系,然而多数程序员的问题不在于分析过度。
学习成功项目的开发经验。Jon Bentley 认为你应该坐下来,准备一杯白兰地,点一根上好的雪茄,想看优秀小说一样来阅读程序。至少应该研究高层设计,并有选择地去研究某些地方的细节源代码。
Thomas Kuhn 指出,凡是成熟的学科都是从解决问题发展起来的。
不仅要阅读别人的代码,还应渴望了解专家对你的代码的看法。
诚实
理论上当然是这样的,然而经常会遇到的讨厌的事情并不是重复性的,而是每次都是不同的恶心事情,或者 lead 催很紧,没有时间做类似的工具。
不要坚持,如果花了 15 分钟调试仍然没有进展,就该放弃排错过程,让潜意识仔细品品。和计算机错误斗气是不明智的,更好的方法是避开他们。
人们还荒唐地强调程序员有多少经验。“我们需要有五年以上 C 语言编程经验的程序员”就是愚蠢的说法。如果程序员过了前一两年还没有学好 C 语言,那么再过个三年也没有什么意义。
最后一个问题,如果你工作十年,你会得到十年经验还是一年经验的十次重复?必须检讨自己的行为,才能获得真正的经验。只有坚持不懈地学习,才能获取经验;如果不这样做,无论你工作多少年,都无法获得经验。
彻夜编程让你感觉像是世界上最好的程序员,却要花上几个星期去纠正你在短暂辉煌时埋下的错误。可以热爱编程,但是热情不能代替熟练的能力,请想明白什么更重要。
不能用“没有习惯”来代替“坏习惯”,只能用“新习惯”代替“坏习惯”
基于问题域编程,最顶层代码不要关心实现细节。
© 2016-2022 Yifei Kong. Powered by ynotes
All contents are under the CC-BY-NC-SA license, if not otherwise specified.
Opinions expressed here are solely my own and do not express the views or opinions of my employer.
友情链接: MySQL 教程站