The Joel on Software Translation Project:让错的程序看得出错

From The Joel on Software Translation Project

Jump to: navigation, search

让错的程序看得出错

作者:周思博 (Joel Spolsky)
译:Paul May 梅普华
Wednesday,May 11,2005
A part of Joel on Software,http://www.joelonsoftware.com

时间回到1983年九月,我第一个真正的工作是在以色列的Oranim。这家大型面包工厂每晚都用六个货机般大的巨型炉子烤出为数十万的面包。

我第一次走进那家面包厂时觉得里头实在脏得离谱。炉壁发黃机器生锈而且到处都是油。

「这里一直都这么脏吗?」我问道。

「什么?你讲这什么话?」经理回答说。「我们才刚打扫过。这已经是几周以来最干凈的时候了。」

说得真好!

我花了好几个月每天早上打扫才真正了解他们的意思。对面包工厂来说,干凈是指机器里没有生面团在烤,垃圾堆里没有发酵的面团,而且地板上也没有堆生面团。

干凈并不是指炉子漆得雪白亮丽。炉子大概十年才会漆一次,并不会每天都来一回。干凈也不是说把油擦得干干凈凈。事实上很多机器都得定期上油,一层薄净的油通常暗示机器刚做过清洁保养。

面包工厂里这整套干凈的概念都得经由学习而来。圈外人不可能走进去就能说出哪里干凈哪里脏。圈外人绝不会想到要看面团滚圆机(把方面团滚成球形的机器,见右边附图)內壁有没有刮干凈。圈外人会觉旧炉子外壁镶板掉色是有问题的,因为镶板很很显眼。不过面包师傅根本不在意炉子的涂漆开始发黃。因为面包的味道还是一样棒。

在面包工厂待两个月,你学会如何「看出」干凈。

程序代码也是一样的。

当你刚开始写程序或尝试读用新语言写的程序时,所有程序代码看起来都一样神秘不可解。而在了解该种程序语言前,你连明显的语法错误都看不出来。

在学习的第一阶段,你会开始发现一种我们通常称为「编程风格」的东西。于是你开始注意那些不遵循缩排标准的程序代码和使用多个大写字母的变量。

也就是这个阶段你会说:「该死的混蛋,我们这里一定要定出一些一致的编程风格!」 然后第二天写出一份你们团队用的编程风格,接下来用六天来讨论One True Brace Style(译注:就是K&R style),然后再花三星期把旧程序代码改写成符合One True Brace Style,一直做到经理发现并责怪你把时间浪费在不能赚钱的事为止。你想想其实不需要一次全部改好,看到哪里改到哪里也没什么关系。于是有一半的程序代码已经改成True Brace Style,而没多久你就忘记这件事了。接下来你就开始满脑子想著其他与赚钱无关的事,比如把某个字串类别换成另一个字串类别等等。

当你对某特定环境下的程序愈来愈精通时,就会开始学著看到其他东西。那些东西可能完全合法并符合编程风格,却又会让你担心不已。

举例来说在C语言里:

char* dest,src;

这是合语法的程序代码;这可能符合你的编程规范,甚至可能是故意这样写的,不过如果你写C的经验够,就会注意这种写法把dest声明成字符 指针却把src声明成字符而已,这可能是你的意思,不过也可能不是。反正这段程序看起来有点不对劲。

来看更细微的例子:

if (i != 0)
    foo(i);

这段程序是百分之百正确的;它符合大多数的编程规范也完全没有错误,不过你可能会质疑if敘述所接的单敘述主体并未用大括号包起来,因为你脑子里想到有人可能会插入另一行程序代码

if (i != 0)
    bar(i);
    foo(i);

...又忘记加上大括号,结果让foo(i)变成永远会执行!所以当你看到没有用大括弧包起来的程序代码区段时,可能就会感觉到一丝丝让你不舒服的气味。

好啦,到目前为止我已经提到三种程序员的成就层级:

1。你不知道干凈和脏有什么分别。

2。你对干凈有粗浅的认知,主要以是否符合编程规范为准。

3。你开始能嗅出藏在表面下不对劲的蛛丝马迹。你会察觉这是问题并且找出来修正。

不过其实还有更高的层次,而这也就是我真正要说的:

4。你有计划地架构程序代码,借助能察觉问题的灵眼让程序代码更正确。

这是真正的艺术:仔细地设计让错误显而易见的编程规范,藉此制作出稳固的程序。

所以现在我要带你看一个小例子然后再展示一个通用的规则。你可以利用这个通则设计出创造增加程序稳固的编程规范。最后我会把主题导引到为某种匈牙利命名法(可能不是让人们晕到的那种)进行辩护,并且批判某些环境(也可能不是你最常用的那种环境)下的异常处理。

不过如果你深信匈牙利命名法不是好东西,认为异常处理是从自巧克力奶昔以来最棒的发明,而且完全不想听听其他意见,没问题,你可以改去罗力那里看看好看的漫画;反正你在这里也没什么好看的;事实上在一分钟內我就会拿出实际的程序代码范例,这些范例很可能会让你在不爽前就晕睡过去了。没错。我想我的计画是把你哄到沉沉入睡,趁你睡著无法抵抗时把「匈牙利命名法=好,异常处理=坏」的想法偷偷塞进你脑子里面。

一个例子

好了。提到这个例子。让我们假裝你正在写某种web应用程序,因为这阵子小朋友似乎都流行写这玩意。

现在有一种叫跨站脚本漏洞(Cross Site Scripting Vulnearability)的安全漏洞,缩写为XSS。我在这里不谈细节:你只需要知道在写web应用程序时,一定要小心绝不能把使用者填入表单的任何字串直接传回来。

举例来说,如果你有一个网页会让使用者在编辑框输入姓名,传送后就会跳到另一个写著「你好啊,张三!」(假设使用者的名字是张三)的网页。很好,这就是个安全漏洞,因为使用者可能不输入「张三」而输入某种奇怪的HTML及JavaScript,这些奇怪的JavaScript就可能会做些低级事情,比如读出你写的cookie內容转送到坏人的坏网站去。而这些低级事现在看起来就是你搞的鬼。

让我们把程序用伪码的方法写出来。想像以下的程序

s = Request("name")

会由HTML表格读取使用者输入(一个POST的参数)。如果你曾经写出下面的程序代码:

Write "你好," & Request("name")

那你的网站已经有让XSS攻击的漏洞了。光这样就够了。

你必须在复制回HTML之前先编码才能避免这个漏洞。所谓编码就是把"换成",把>换成>,如此类推。所以

Write "你好," & Encode(Request("name"))

是绝对安全的。

所有来自使用者的字串都是不安全的。任何不安全的字串都得先编码后才能输出。

让我们尝试设计一组编程规范,确保当你犯这种错时程序代码看起来就是错的。如果程序代码有错(至少看起来错),就很有机会被修改或审视这段程序的人抓到。

可能方案一

方案一是将所有字串立即编码,由使用者取得后马上就进行:

s = Encode(Request("name"))

所以我们的规范会写著:如果你看到没有被Encode包住的Request,程序一定是错的。

你开始训练自己的眼睛找寻落单的Request,因为它们违反规范。

这是有用的,因为只要你遵循规范就不会有XSS问题。不过这并不是最好的架构。比方说你可能想要把这些使用者字串存到数据库里,这时候储存以HTML编码过的字串并不合理,因为字串有可能会用在HTML网页以外的场合。假如是信用卡处理程序要用时编码过的资料就会产生问题。大部份web应用程序开发都会依循一个原则:所有字串在內部都是编码的,要等到送至HTML网页的前一瞬间才会处理,因此这可能并不是正确的架构。

我们真的要能让字串维持在不安全格式一段时间。

好吧,我再试看看。

可能方案二

如果建立一种编程规范,要求在写出任何字串时必须加以编码,是否可以满足要求吗?

s = Request("name")
// 很后面:
Write Encode(s)

现在当你看到一个落单没有Encode跟著的Write时就知道有有问题了。

唉,这也不太好……有时候你的程序里会有一小段的HTML码,这种情况下是不能够编码的:

If mode = "linebreak" Then prefix = "<br>" // 很后面: Write prefix

这照我们的规范来看是错的,我们必须要在输出时加以编码:

Write Encode(prefix)

不过现在应该要新增一行的"<br>"却被编码成&lt;br&gt;,结果变成使用者可以看到的字符< b r >。这样的解法也不对。

所以说有时候你不能在读入字串时编码,有时候你也不能在输出时编码,这两种提案都不能用。可是没有适当的编码规范,我们还是有出下列问题的风险:

s = Request("name") ……好几页之后…… name = s ……好几页之后…… recordset("name") = name // 把名字存在数据库中的姓名栏 ……好几天后…… theName = recordset("name") ……好几页甚至好几个月之后…… Write theName

我们还会记得要对字串编码吗?你在任何单一的地方都看不到问题。连可以嗅的地方都没有。如果这种程序有一大缸子,要一大票侦探才能追踪出所有字串的来源并确认是否已编码。

正解

所以让我提议一种能用的编程规范。我们只有一个规则:

所有来自使用者的字串都必须存在以"us"(表示Unsafe String,不安全字串)为字首的变量(或数据库栏位)中。所有经HTML编码或来自确认安全来源的字串都必须存在以"s"(表示Safe String,安全字串)为字首的变量中。

让我们重写程序,只是依规范重新命名变量,其他完全不动。

us = Request("name") ……好几页之后……
usName = us
……好几页之后……
recordset("usName") = usName
……好几天后……
sName = Encode(recordset("usName"))
……好几页甚至好几个月之后……
Write sName

新规范中值得注意的是,只要遵循编码规范,不安全字串相关的错误一定可以由单一行的程序代码看出来

s = Request("name")

是之前的错误,因为你可以看到Request的结果被指派给以s开头的变量,这违反了规则。Request的结果一定是不安全的,所以必须指派给以"us"开头的变量。

us = Request("name")

一定没问题。

usName = us

一定没问题。

sName = us

一定是错的。

sName = Encode(us)

一定是对的。

Write usName

一定是错的。

Write sName

没问题,下面也一样没问题

Write Encode(usName)

每一行程序光是看程序代码本身就足以检查,而且如果每一行程序都对,组合起来整个程序也是对的。

终于好了,利用这套编码规范,你的眼睛学著看到Write usXXX就知道是错的,而且你也立即知道要如何修正。我知道一开始要看到错误的程序是有一点难,不过进行三个星期后你的眼睛就会习惯,就像面包厂的工人看到大面包工厂就会马上说:「搞什么鬼,这里都没人在扫哦!这算啥面包厂。」

事实上我们可以再把规则延伸一点,把RequestEncode函数改名(或封裝)成UsRequestSEncode……换句话说,传回不安全字串以及安全字串的函数要和变量一样,分别要用UsS作为字首。现在看看程序代码:

us = UsRequest("name")
usName = us
recordset("usName") = usName
sName = SEncode(recordset("usName")) Write sName

看到我们的成果没?现在你可以看看等号两边的字首是否相同就能找到错误。

us = UsRequest("name") // 没问题,两边都以US开头 s = UsRequest("name") // 错 usName = us // 对 sName = us // 一定错。 sName = SEncode(us) // 一定对。

我还能再进一步把Write改名成WriteS并把SEncode改名成SFromUs

us = UsRequest("name")
usName = us
recordset("usName") = usName
sName = SFromUs(recordset("usName")) WriteS sName

这使得错误更加显而易见。你的眼睛会学习「看出」可疑的程序代码,另外这也能协助你经由一般撰写或阅读程序代码的动作找到隐藏的安全漏洞。

让错的程序看得出错是很棒没错,不过却不是所有安全问题的最佳解答。它无法找到所有可能的问题或错误,因为你可能没法子看过每一行程序代码。不过绝对比什么都不做要好,而我很希望有套编码规范能让错误的程序代码至少看起来是错的。你马上就能获得好处,每当程序员的眼睛扫过一行程序,就能检查并防止某些特定的错误。

一个通则

这种让错误程序看起来错的作法有个前提,就是要让对的东西在屏幕上紧靠在一起。当我看到某个字串时并要決定 程序代码正确与否,我必须知道字串出现的所有位置以及字串是安全的还是不安全的。我不希望这些资料出现在另一个文件或是要卷动画面才能看到的另一页。我必须能当场看到,而这说的就是一套变量命名规范。

有很多其他的例子可以说明,只要把某些东西搬在一起就可以改善程序代码。大多数的编程规范都有如下的规则:

  1. 保持函数名称简短。
  2. 变量声明的地方离使用的位置愈近愈好。
  3. 不要用宏建立你个人专属的程序语言。
  4. 不要使用goto
  5. 不要让右括弧离左括弧超过一个画面。

这些规则有一个共同点,就是尽量让一行程序代码实际作用的相关资讯在画面上愈近愈好。这样能提高眼球找出程序实质运作內容的机会。

大体上我得承认我有点害怕会藏东西的程序语言功能。当你看到程序代码

i = j * 5;

……就C来说你至少会知道j会乘以5而结果会存到i

不过如果你在C++里看到相同的片段,你什么都不知道。在C++中唯一能知道真正发生什么事的方法就是找出ij所属的类型,而这个类型可能会在完全不一样的地方声明。因为j运算子*可能有重载,在你要做乘法时会做些很机灵的事。而i运算子=可能也是重载的,而两者类型可能是不相容的,于是又呼叫到某个自动类型强制转换的函数。光是检查变量的类型还不足以确认,还得检查实现该类型的程序代码才行,万一实现时又有继承其他类型就更麻烦了,因为你得回溯类别继承的祖宗八代才能找到真正的程序代码,不巧又有用到别处的多态就真的有大麻烦了,因为光是知道ij声明的类型并不够,还得知道它们此刻的类型,这不知道要看多少的程序代码,而且依照计算理论的停机问题,你永远都不能真的百分之百确定自己已经看完所有地方了(啊啊啊啊啊!!!)。

当你看到C++的i=j*5时你只能自求多福了,兄弟。这对我来说就降低了光看程序代码找出在问题的能力。

当然啰,理论上这应该没什么关系。当你做些重载运算子*之类聪明事时,只要为了要提供一个优美而安全的抽象罢了。天啊,其实j是个Unicode字串类型,一个Unicode字串乘以一个整数显然是把正体中文转成简体中文的良好抽象作法,对吗?

问题当然出在没有绝对安全的抽象方法。我已经在抽象出错定律里讨论很多了,所以不会在这里重复。

Scott Meyers示范了各种抽象出错(至少是C++)的型式以及所造成的伤害,他靠这个主题就创出一番事业了。(顺便一提,Scott的书Effective C++第三版刚刚上市;整本书都重写过; 今天就去买一本吧!)

好吧。

有点失焦了。我最好回顾一下到目前为止的內容:

找出能让错误程序看起来错的编程规范。让正确的资讯集中在程序代码中相同的地方,方便你看出某些问题并立即修正。

我是匈牙利

我们现在回到恶名昭彰的匈牙利命名法。

匈牙利命名法是微软程序设计师Charles Simonyi发明的。Simonyi在微软做的主要计划是Word;事实上他还主持了世界上第一个所见即所得的文书处理器(在Xerox Parc名为Bravo计划)。

在所见即所得的文书处理中会用到可卷动的视窗,所以座标值有两种意义:相对于视窗或相对于处理页。两种座标的差异很大,所以好好安排是非常重要的。

我猜这正是Simonyi开始采用某些之后被称作匈牙利命名法的原因之一。它看起来像匈牙利文,而Simonyi是从匈牙利来,所以以匈牙利为名。在Simonyi版本的匈牙利命名法中,每个变量都会加一个小写的字首,表示变量內容的种类。

打个比方,如果变量名为rwCol,rw就是字首

我是故意用种类(kind)这个词,因为Simonyi在他的文章中误用了类型(type),结果好几世代的程序员都误解了他的意思。

如果你仔细读Simonyi的文章,就会发现他所讲的和我之前范例所用的命名规范是一样的,在我的范例中把uss分别定义为不安全字串和安全字串。这两者的类型都是字串。如果你把某种字串指派另一种,编译器并不会给任何警告,Intellisense也不会说些什么。可是他们的语意是不同的;他们解读和处理的方式都不同,要把两种字串互相指派时还要某些转换函数做转换,否则就会有执行时期的问题。你好运。

微软內部称Simonyi对匈牙利命名法的原始概念为应用匈牙利命名法,因为它用于应用程序部门,也就是Word及Excel。在Excel的原始程序代码里有大量的rwcol,你看到这些字首就知道它们指的是行(row)和列(column)。没错,它们都是整数,可是两者间的转换完全没有意义。有人告诉我说Word的程序代码里有大量的xlxwxl代表相对于排版页面的水平座标,而 xw则代表相对视窗的水平座标。两者都是整数但却是不能互转的。两个程序里都有很多cb,意思是字节的个数。没错,这也是整数类型,不过光看变量名就可以得到更多资讯:这是字节的个数,也就是缓冲区的大小。另外如果你看到xl = cb就可以拉警报了。这显然是错的程序,虽然xlcb都是整数,可是把以像素为单位的水平位移设成字节个数绝对是疯了。

在应用匈牙利命名法中字首可以用于函数和变量。因此虽然我真的没看过Word的源代码,我还是敢打赌Word里一定有个叫YlFromYw的函数,可以把垂直方向的视窗座标转成垂直方向的排版页座标。应用匈牙利命名法用TypeFromType取代传统的 TypeToType,这样每个函数名就会以传回的类型开头,这正与我稍早在范例中把Encode改名为SFromUs的作法相同。事实上在正规的应用匈牙利命名法中Encode函数一定要改名为SFromUs。应用匈牙利命名法在该函数命名上并没有提供其他选择。这其实是件好事,因为你少一件事要背,另外也不必担心Encode究竟是用什么类型。程序也变得精确多了。

应用匈牙利命名法非常有用,特别是当初C语言盛行,而编译器尚未提供很有用的类型系统时。

不过接下来却出了一些问题。

黑暗世界占用了匈牙利命名法。

似乎没有人知道为什么或是如何发生的,不过似乎是视窗团队中写文件的人不小心创造出后来名为系统匈牙利命名法的东西。

某处有人读了Simonyi的文章看到里面用了「类型」这个字眼,因此认为作者指的就是类型,意思就像是类别或是类型系统中,或是编译器所做的类型检查。 其实不然。作者很小心并精确的解释他用「类型」这个字的意义,不过没有用。伤害已经造成了。

应用匈牙利命名法的字首很有用而且有意义,"ix"表示数组索引,"c"表示个数,"d"表示两个数字间的差(比如"dx"表示「宽度」),如此类推。

系统匈牙利命名法的字首作用就差多了,"l"表示长整数,"ul"表示正长整数而"dw"代表双字(呃,事实上就是正长整数)。在系统匈牙利命名法中,字首只能告诉你变量真正的资料类型。

这误解了Simonyi的意图和实现,差异虽细微实质上却是完全不同。这件事唯一的教训是让你知道,如果你写出些没人能懂的艰深难解学术文章,你的想法可能会一再被误解,结果变得非常荒谬,完全违背你的原意。所以在系统匈牙利命名法中会出现大量的dwFoo表示「双字的某某」,可恶的是某个变量是双字这件事对你几乎是完全没用的。难怪大家都很讨厌系统匈牙利命名法。

系统匈牙利命名法的流传既深又广;它是整个视窗程序设计文件的标准;Charles Petzold的视窗程序设计(学习视窗程序设计的圣经)等书籍更为它广为宣扬,很快的它也成为匈牙利命名法的主要势力,即使在微软內部也一样。在微软內也只有少数不在Word和Excel团队的程序员了解他们搞出什么样的错。

接下来就是大反抗了。有群程序员们从一开始就没搞懂过匈牙利命名法,他们发现自己用的竟是烦人又几近无用的分支,于是就起来反抗。不过系统匈牙利命名法里还是有些好东西可以帮你看出问题。如果用系统匈牙利命名法,至少会在使用时知道变量类型。不过没应用匈牙利命名法那么有价值就是了。

大反抗在.NET第一版发行时到达巅峰,那时微软终于告诉大家「不建议使用匈牙利命名法」。这还真是欢声雷动啊。我根本不认为微软会花心思解释原因。他们只是扫瞄文件中命名指引的章节然后加上「不要使用匈牙利命名法」的字句。当时匈牙利命名法非常不受欢迎所以没有人会真的抱怨,而除Excel及Word以外的人都因为不必再用这么麻烦的命名规范而松了一口气,他们认为在有强类型检查及Intellisense的时代也不需要这种规范。

不过应用匈牙利命名法还是很有价值的,它加强了程序代码的连结让程序代码更易阅读,撰写,除错及维护,最重要的是它让错误的程序看得出错。

在继续之前还有一件事我说过要做,就是再骂一次异常处理。我上次这样做惹来很多麻烦。我在周思博趣谈软件首页上一篇即兴的评论中写说我不喜欢异常处理,因为它实际上就是隐藏的goto,我认为这比看得到的goto更糟糕。当然就有几百万人跑出来痛骂我。全世界唯一跳出来替我辩护的当然也就是Raymond Chen。顺带一提,他既然是世界上最好的程序员,当然得出来讲讲话,对吗?

这篇文章讲到异常处理的重点了。你的眼睛学著看到错误的程序代码,这样就能防止问题发生。为了让程序能变得真正稳固,进行程序代码检视时得有一套能集中资讯的命名规范。换而言之,你眼前有关程序运作的资讯愈多,寻找错误的结果愈好。当你看到以下的程序代码时

dosomething();
cleanup();

...你的眼睛会说没什么问题啊。我们总是要做清除的动作!不过dosomething有可能会引发一个异常,所以有可能不会呼叫cleanup。用finally等很简单就能修正这个问题,不过这并不是我的重点:问题在于要知道cleanup一定会被呼叫到的唯一方法,就是调查整个dosomething呼叫树,看看是否有任何场合会产生异常。这也还好,可控制式异常处理(checked exception)可以让你不用那么辛苦,不过重点是异常处理把资讯分散开来了。你得去看其他地方才能知道程序能正确执行,所以无法运用你眼睛天赋的功能去学习看出错的程序代码,因为根本没东西可看。

如果我写个小脚本程序,只是每天一次到处收集资料然后印出来,这时候异常处理好用得不得了。我只想忽略所有可能出错的地方,直接把整个程序用一个大try/catch包起来,如果有出什么问题就用catch把错误电邮给自己。异常处理对简单随便写的程序很有用,对脚本程序或是不是非常重要或无关生死的程序也不错。不过如果你在写一套操作系统或核电厂程序,或是用于开心手术的高速电锯,异常处理可是危险的很。

我知道大家会认为我是个无法正确理解异常处理的笨程序员,完全不知道只有当我衷心接纳异常处理后它才能改善我的生活。这种想法真是太糟糕了。想要写出真正可信赖的程序代码,应该要尝试用考虑到人有弱点的简单工具,而不是靠那些提供有问题的抽象并把副作用隐藏起来,还认为程序员绝不出错的复杂工具。

补充读物

如果你还是衷心于异常处理,读读Raymond Chen的文章更干凈更优雅,不过更难读。「异常处理用得正确与否,很难由程序代码看得出来... 异常处理太难了,我实在不够聪明无法掌握。」

Raymond对致命宏的文章A rant against flow control macros讨论了另一个让资讯分散导致程序无法维护的例子。「当看到使用[宏]的程序代码时,你必须看遍各个头文件才能了解它们的作用。」

想要了解匈牙利命名法的历史背景,可以由Simonyi的原文匈牙利命名法开始。Doug Klunder在另一篇比较清楚的文章中把它引进Excel团体 。想知道更多匈牙利命名法的故事以及如何被文件撰写人破坏的始末,可以去看Larry Osterman站上的贴文,特别是Scott Ludwig的评论,或是Rick Schaut贴的文章。

我不写软件文章时就在制作FogBugz:一套名字笨笨的聪明项目管理软件。现在就去看看(还有免费在线试用)。我们才刚推出大升级版FogBugz 4.0!

这些网页的內容为表达个人意见。
All contents Copyright © 1999-2005 by Joel Spolsky。All Rights Reserved。

软件随想录:程序员部落酋长Joel谈软件(local.joelonsoftware.com/wiki)-23相关推荐

  1. 软件随想录:程序员部落酋长Joel谈软件(阮一峰译)-2

    2. 寻找优秀的程序员 2006年9月6日,星期三 优秀的程序员都在哪里 这是你第一次公开招募雇员.如同大多数人一样,你会发布广告,可能也会浏览一些大型的网上论坛,然后你就收到了一吨的简历. 一份份看 ...

  2. 软件随想录:程序员部落酋长Joel谈软件(阮一峰译)-3

    3. 寻找优秀的程序员之实战指南 2006年9月7日,星期四 你是一个雇主.你在所有正确的地方刊登了招聘广告,你有一个完善的实习生制度,你面试了所有你想要的人.但是很不幸,如果优秀的程序员不愿意为你工 ...

  3. 【连载】听程序员部落酋长畅谈关于软件的人和事-节选3

    寻找优秀的程序员之实战指南 --选自<软件随想录:程序员部落酋长Joel谈软件> [好消息]本书今天(12.10)互动有货,感兴趣的朋友可以 逛逛去 2006 年 9 月 7 日 ,星期四 ...

  4. 程序员部落酋长 Joel 之洞见

    软件开发随想集-- 程序员部落酋长 Joel 之洞见(暂名) [原书名] More Joel on Software [内容提要] 本书是一部关于软件技术.人才.创业和企业管理的随想文集,作者以诙谐幽 ...

  5. ios查看帧率的软件_程序员必看!直播软件开发弱网下保障高清流畅推流的方法...

    通常情况下程序员在开发直播软件时,优化卡顿和延迟是比较多的,只要是优化欠佳,就会导致前端APP运行出问题,为了帮助程序员在开发过程中能更加合理有效的优化,小编在这里从开发的层面简要介绍一下影响直播体验 ...

  6. 软件随想录(local.joelonsoftware.com/wiki)-2002年03月13日 约耳的程序员书柜 - Book Reviews

    2002年03月13日 约耳的程序员书柜 - Book Reviews 约耳的程序员书柜 From The Joel on Software Translation Project Jump to: ...

  7. 为程序员更新了Joel测试

    前一段时间-确切地说是2000年-乔尔·斯波斯基(Joel Spolsky)写了一篇博客文章,标题为:" 乔尔测试:改进代码的12个步骤 ." 许多软件工程师和开发人员使用此测试来 ...

  8. chrome java插件_Java程序员喜欢的10款软件里有你在用的吗?

    作为一名Java程序员,日常开发的过程中,我们需要借助很多工具来进行编码.好的工具可以极大的提升程序员的工作效率,今天我们来认识下大多数程序员喜欢的10款软件.持不同意见或有想要补充的小伙伴,欢迎评论 ...

  9. 请不要叫我“程序员”,我是一名软件工程师--读《走出软件作坊》1

    周星驰有一句经典的台词:请不要叫我"跑龙套的",我是一名演员. 看了这本书,我突然也有了感悟:请不要叫我"程序员",我是一名软件工程师. 程序员只关心自己代码的 ...

最新文章

  1. ExtJs中表格用例代码
  2. childnodes 兼容性问题
  3. 【Flink】flink-connector-elasticsearch5与flink-connector-elasticsearch6 有什么区别
  4. asp.net MD5数据加密和解密
  5. 实习成长之路:MySQL六:行锁的功与过:怎么减少行锁对性能的影响?
  6. java打包apk_APK打包流程
  7. 知识问答题小程序头脑王者源码
  8. 利用武汉理工大学学校图书馆资源查论文 以使用中国知网查阅论文
  9. 计算机保研面试英文,计算机保研面试英文自我介绍范文
  10. 应用程序清单 Manifest 中各种 UAC 权限级别的含义和效果
  11. React导入json数据
  12. 终于被我搞掂了 Vue3 + Element 的正确打开方式(直接拿来就用)
  13. c++二维数组定义与初始化
  14. Qt Creator 添加大恒相机SDK库
  15. 教你认识各种分辨率的英文缩写
  16. 重邮2019计算机复试准备工作相关
  17. ElasticSearch Aggs的一些使用方法
  18. Tennessee Eastman(TE)田纳西-伊斯曼仿真平台应用试验与分析
  19. 【LeViT: a Vision Transformer in ConvNet’s Clothing for Faster Inference论文解读】
  20. 电脑上下载mysql是不是很慢_为什么电脑下载速度很慢?

热门文章

  1. 淘宝叠猫猫瓜分3亿红包赚猫币自动生成脚本(无需安装其他软件),附每天最大限度的领取最多数量的喵币方法
  2. VS2015编译Qt5.7.0生成支持XP的静态库
  3. GPIO_PULLUP,PULLDOWN, NOPULL
  4. 诺基亚A7Android,诺基亚转战安卓 各操作系统代表机型推荐
  5. 机器分类模型在肺癌病例中的比较研究
  6. python 更换桌面壁纸 播放音乐
  7. Postgresql如何授权未来会创建的表(避免反复授权)
  8. AFL(American Fuzzy Lop)源码详细解读(3)
  9. excel时间排序之按年和月排序方法
  10. python回合制游戏教程_Python基础之面向对象(回合制游戏)