好奇的探索者,理性的思考者,踏实的行动者。
Table of Contents:
由于程序员的工作最近几年比较容易找,工资还不错,所以很多程序员往往只看到自己的肚脐眼,看不到自己在整个社会里的位置其实并不是那么的关键和重要。很多程序员除了自己会的那点东西,几乎对其它领域和事情完全不感兴趣,看不起其他人。
这就是为什么我的前同事 TJ 作为一个资深的天体物理学家,在一个软件公司里面那么卑微。貌似会写点 node.js,iOS 软件的人都可以对他趾高气昂的样子,而其实这些东西的价值哪里可能跟 TJ 知道的物理知识相提并论。很多科学家其实都可以轻而易举的掌握程序员知道的那点东西。
其实软件里面有少数永恒的珍宝,可惜很少有人理解和尊重它们的价值。这在其它的工程领域看来是不可思议的,然而这却是事实。由于没有科学作为理论的基础,没有实验作为检验它们的标准,软件行业的很多东西就像现代艺术一样,丑陋无比的垃圾还能摆在外表堂皇的“现代艺术博物馆”里面,被人当成传世大作一样膜拜。
你的软件是什么语言写的,告诉别人的时候是千万要小心的,不到万不得已最好不要说。因为十有八九,对方会立即在心里对你的软件的价值做出判断,光凭你用的是什么语言。
在这样一个行业里,你会很难找到一个只把程序语言或者技术当成是工具的人。如果有人问你对某个语言或者技术的评价,是非常尴尬甚至危险的事情,所以最可靠的办法就是不做评论,什么都不要说。
在任何领域,都只有少数知识是精髓的,另外大部分都是表面的,肤浅的,是从精髓知识衍生出来的。精髓知识和表面知识都是有用的,然而它们的分量和重要性却是不一样的。所以必须区分精髓知识和表面知识,不能混为一谈,对待它们的态度应该是不一样的。由于表面知识基本是死的,而且很容易从精髓知识推导衍生出来。我们不应该因为自己知道很多表面知识,就自以为比掌握了精髓知识的人还要强。不应该因为别人不知道某些表面知识,就以为自己高人一等。
IT公司经常有这样的人,以为精通一些看似复杂的命令行,或者某些难用的程序语言就很了不起似的。
在很多程序员的脑子里,所谓的“流程”和“测试”,比真正解决问题的代码还重要。他们跟你说起这些,那真的叫正儿八经,义正言辞啊!所以有时候你很迷惑,这些人除了遵守这些按部就班的规矩,还知道些什么。大概没有能力的人都喜欢追究各种规矩吧,这样可以显得自己“没有功劳有苦劳”。这些人自己写的代码很平庸,不知道如何简单有效地解决困难的问题,却喜欢在别人提交代码让他review的时候叫喊:“测试很重要!覆盖很重要!你要再加一些测试才能通过我的review!”
学习时注意细节:包括推论的关键步骤,单词的拼写,代码的格式,
错误的容忍度的处理
编程要能通过计算机解决实际问题,比如使用什么数据结构和算法,即问题模式。
编程是人,而且是大量人来进行的,所以它还必须解决语言代码模式和复用能力的问题。即软工问题。即设计模式。
再者,编程跟语言有关,所以它必须首先解决语言复杂度问题,即语言的语法;比如 OO,即代码模式。
对于软件的复杂度,唯一有效的推测方法是依据经验。而且还不是时时都好用。作为一个程序员,我知道,根据我之前开发过的相似的功能特征,我可以估计出现在的这些功能特征各自要多少开发时间。然后,我把总时间加起来,这就得到了完成整个项目需要的大致时间。然而,事实情况中,每个项目在开发过程中都遇到二、三个瓶颈。这些瓶颈会肆意的消耗程序员的大量时间,你在遇到它们之前根本不会有所预见。它们会拖住整个项目,致使工期延后数周甚至数月。
这篇经典论文的核心论述通常被解释为复杂的软件工程问题无法靠简单的答案来解决。
次要和必要复杂度
在该论述当中,讨论到了次要和必要复杂度的差异。所谓次要复杂度是指由人们本身所产生的问题,而这类型的问题是可以被解决的。譬如说,撰写和最佳化组合语言的复杂度就是属于次要的,它可以借由高阶程序语言如Java来取代。必要复杂度则是从软件本身要解决的问题衍生而来,并无法被移除。如果软件需要提供三十个不同的功能,那么这三十个功能都是必要的,这些功能都必须被实作出来。
软件工程面临的问题在于我们已经清除了大部分的次要复杂度,而剩余的(主要复杂度)都无法改变。
在移除次要复杂度中最大的进展也许要算是高阶语言的诞生,像是Fortran和Java。
在欧洲中世纪的传说中,有一种叫"人狼"的妖怪,就是人面狼身。它们会讲人话,专在月圆之夜去袭击人类。而且传说中对"人狼"用一般的枪弹是不起作用的,普通子弹都伤不到也打不死它,只有一种用银子作成的特殊子弹才能把它杀死。Brooks在他最著名的随笔文章《No Silver Bullet》里引用了这个典故 ,说明在软件开发过程里是没有万能的终杀性武器的,只有各种方法综合运用,才是解决之道。而各种声称如何如何神奇的理论或方法,都不是能杀死"软件危机"这头人狼的银弹。他当时大胆声称并预言方法学家们10年之内绝找不到什么极好的的神奇银弹。他的文章发表后,被广泛引用,后来他的随笔结集成书,《人月神话》。从此,在软件界,银弹(Silver Bullet)成了一个通用的比拟流行开来。1975年所出版的《人月神话》—被称为软件工程圣经。
僵尸代码就因为各种原因如(有可能是程序库升级,老的接口不再使用。有可能是需求调整)导致的没用的、但却留在程序库中的代码。这样的代码没有任何其它程序会调用它,没有任何函数、对象引用它。
死代码是应该删除的代码。没有用的代码也就是没有人维护的代码。最后将变成没有人知道它是有什么用处的代码。后来的人也开始不敢删除这样的代码,怕万一什么地方需要用到它。于是这样的死代码积累的越来越多。技术债务越来越重。健康的项目慢慢腐烂变质。最终没有人能维护。
死代码应该及时大胆清除。即使错误了也是svn呢
很多人以为看大型项目可以提升自己,而没有看到大型项目不过是几十行核心代码的扩展,很多部分是低水平重复。几十行平庸甚至晦涩的代码,重复一万次,就成了几十万行。看那些低水平重复的部分,是得不到什么提升的。造就我今天的编程能力和洞察力的,不是几百万行的大型项目,而是小到几行,几十行之短的练习。不要小看了这些短小的代码,它们就是编程最精髓的东西。反反复复琢磨这些短小的代码,不断改进和提炼里面的结构,磨砺自己的思维。逐渐的,你的认识水平就超越了这些几百万行,让人头痛的项目。
3个人搭档做一个模块,每天要抽出一点时间来跟其他两位阐述自己的代码,这样可以使这三个人更加了解这个模块,也可以顺便再梳理下自己的思路,及相互监督的作用,以及完成之后可以让任何一个人来维护这个模块
项目是在原来的系统上开发的要问清客户原来的系统的优缺点,因为顾客会在新旧系统之间计较,不爽的地方会即刻发现
void foo(char *str)
{
char buf[10];
strcpy(buf, str);
......
}
如:编译的时候编译的是debug版本的,但是依赖的第三方库是release版本的,这种混合使用不同版本的库可能会导致程序闪退或出现各种异常。
原因:
1. 不匹配的库版本: Release 版本的库和 Debug 版本的库通常是经过不同编译设置生成的,它们可能具有不同的符号、优化方式等。因此,当 debug 版本的程序尝试使用 release 版本的库时,可能会导致不匹配的符号、链接错误或其他不一致性,从而导致程序崩溃。
fwrite(路径,模式);w写模式,文件的内容就被删掉,追加得用a模式
类中的方法找不到符号,看看是不是命名空间有问题,方法实现中是不是忘了加类名
别在遍历容器的时候进行删除操作,这相当于埋了个不定时炸弹
计算向量的垂直向量时,原向量 vector2D(b.x-a.x, b.y-a.y), 少加了括号误写成 vector2D(-b.y-a.y, b.x-a.x,), 应该是vector2D(-(b.y-a.y), b.x-a.x,),
查看php错误日志
php配置文件、错误日志位置可以从php_info中看到
php本身错误
某些函数找不到,可能是没有安装相应的模块。
安装了相应的模块还是不生效,那就再重启下php-fpm,之后模块就可以找到了。
php数组占用过多内存空间
$startMemory = memory_get_usage();
$array = range(1, 100000);
echo memory_get_usage() - $startMemory, ' bytes';
How much would you expect it to be? Simple, one integer is 8 bytes and you got 100000 integers, so you obviously will need 800000 bytes. That’s something like 0.76 MBs.
but this gives me 14649024 bytes. Yes, you heard right, that’s 13.97 MB - 18 times more than we estimated.
因为要存其他额外的信息,所以多了很多其他的空间,就是c要实现数组结构要维护其他很多的额外信息
foreach循环后留下悬挂指针
在foreach循环中,如果我们需要更改迭代的元素或是为了提高效率,运用引用是一个好办法:php的引用
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
$value = $value * 2;
}// $arr is now array(2, 4, 6, 8)
这里有个问题很多人会迷糊。循环结束后,$value
并未销毁,$value
其实是数组中最后一个元素的引用,这样在后续对$value
的使用中,如果不知道这一点,会引发一些莫名奇妙的错误:)
避免这种错误最好的办法就是在循环后立即用unset函数销毁变量:
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
$value = $value * 2;
}unset($value); // $value no longer references $arr[3]
log("last login time : %d", last_login_time);
查程序崩溃
Qt下程序崩溃,但是没有崩溃时的堆栈
若出现崩溃错误,一定要消灭在萌芽中,不可放任不管,因为在刚发现时,往往知道是修改那个功能出现的崩溃,拖得越久,越难以确定崩溃的范围
查找步骤:
1. 查找稳定复现的方法
2. 在代码中加打印,从大范围,到小范围确定出问题的代码段
3. 锁定可疑的代码,如系统调用,容器越界,容器删除等操作
4. 修改可疑代码,验证是否解决
即在调试之前,需要先搞明白系统的运作方式。包括并不限于阅读文档,阅读源码,寻找熟悉系统的同事交流。
然而现实中(尤其互联网公司)大多是 band-aid fix:改两行,似乎能 work 了,宣布 fix,几周之后搞出更大的问题——这就是暗坑的来源。
理解了你自己的系统后,还会获得一个额外的好处。当你找到bug时,必须在不破坏其他地方的前提下修复它们。理解系统行为是不破坏系统的第一步。理解了系统之后,你会明白什么是对的,什么是错的——
为了修复 bug,我们需要找到引发 bug 的条件,确保可以有规律的重现 bug,接下来才是修复:当问题没有修复时,如果你执行 X 操作,失败率为100%;在修复问题后,再执行 X 操作,如果失败率为0,那么你知道bug确实已被修复。
为了有规律的重现 bug,我们应该想办法制造/增加 bug 出现的条件。如果车胎漏气,我们可以把车胎放在肥皂水里,寻找气泡。
如果你不能留意实际情况发生的全过程,那么你极有可能曲解很多问题。你猜测某个地方出了问题,于是修复它,但实际上错误发生在另一个地方。
由于你没有看到一个字节发生了改变,导致用错误的参数调用了一个子例程,或者一个队列溢出,而你却去修复了一个完全没有发生错误的地方。这样,你不仅没有修复问题,而且还可能改变了时序,因此把问题隐藏起来了,这会使你误认为已修复问题。
一定要亲眼看到错误是如何发生的。观察往往比猜测能更快的找到问题:因为猜测虽然看起来是捷径,但这条捷径并不会带你找到问题的根源。
通过反复地把问题分成好的一半和坏的一半,来缩小搜索范围,然后进一步研究有问题的那一半。
1.这叫控制变量法,每次只控制一个变量才能确定次变量对系统的影响
2.如果你所做的更改没有起到预期的作用,那么就把它改回来。它们可能会产生无法预料的影响。
很多 bug 都可以通过查阅 revision history 或观看 repro video 来解决。
当电器出现问题的时候,应该问自己一个古老的、看似愚蠢的问题:“插头插上了吗?”虽然这个问题看上去很愚蠢,但它经常发生。
有时错误的原因在于 typo:你以为你在 getItem,但实际在 getItems。这种问题在 dynamic typed language 里面尤其常见。
与其自己与 bug 死扛,不如去寻求同事的帮助:
别人寻求帮助至少有3个原因(还不算把整个问题甩给别人):获得全新观点、专业知识和经验。而且,人们通常很愿意帮忙,因为这给了他们一个证明自己很聪明的机会。
我们按照自己老一套的思路是很难看清全局的。我们都是普通人,对任何事情都有偏见,包括对bug隐藏在哪里的看法。这些偏见可能导致我们无法看清实际情况。而其他人则会从一个无偏见的角度来看问题(实际上他们只是有另一种不同的偏见),这可能会给我们很大的启发,帮助找到新的方法。
即使无法从他们那里得到帮助,他们也可以安慰你一下,告诉你这个问题真是一个非常棘手的问题,也可以借给你肩膀靠一靠。
无论你想要获得什么样的帮助,在向别人描述问题的时候,一定要记住一件事:报告症状,而不要讲你的理论。之所以要从别人那里获得全新的观点,就是因为你的理论起不到任何作用。如果你找了一个人,把你的理论告诉他,那么也会把他拉到你原来的思维定式中。
当你认为你已经修复了一个设计问题时,取消这个修复,确定系统再次失败。然后再应用这个修复,再次验证问题已修复。直到你经过从修复到失败,再从失败到修复这个过程之后(只应用和取消修复,而不改变其他地方),才能够证明你确实已经修复了问题。
一个函数的实现要调用另一个没用完成对函数,可以写一个假的函数来模拟
Error codes should also be accompanied by descriptive error messages. However, only in rare circumstances should the error message try to predict why the error occurred. It should simply relate what happened
主要逻辑
疑问处
数据库
程序文件
配置文件
注意点
UI
未完成
已通过
收获
线上出的问题
gm命令
时间富裕的情况下的优化
测试要点