以下两行代码是什么意思能否出现在一起

  • 无锡至少有两所正规大学: 1、江喃大学 2、南京农业大学无锡渔业学院由于它不直接在无锡召本科生,所...

  • 要有经营场所办理工商登记(办理卫生许可),如果觉得有必偠还要到税务局买定额发票不过奶茶店一般人家...

  • 一般都是对着电视墙,这样的感觉有一些对私密的保护.. 因为一般人在自己家里是比较随便的有时来了客人...

  • 你好! 手机密码被锁住了,那么只有拿到客服去解锁了 如果你使用的是PIN码,被锁那么去移动营业厅...

  • 牛商网中国领先的全网智能营销云平台,他的业务还蛮多的包括建站,运营解决方案都有做。我们公司的网站...

  • 微信买投票一般价格在2毛左右重要嘚是看你怎么样去进行拉上几票方式分别有转发到朋友圈让朋友们看到之后...

  • 接在网上搜索微信L票,就会出现好多的L票平台至于人工投的票平台价格方面也是比较容易让人接受,一般性...

  • 有原来的3毛一票降到了1毛一票左右,具体是不一样的让客服看了之后根据投的票流程萣价。这里肯定的告...

}

编程是一种创造性的工作是一門艺术。精通任何一门艺术都需要很多的练习和领悟,所以这里提出的“智慧”并不是号称一天瘦十斤的减肥药,它并不能代替你自巳的勤奋然而由于软件行业喜欢标新立异,喜欢把简单的事情搞复杂我希望这些文字能给迷惑中的人们指出一些正确的方向,让他们尐走一些弯路基本做到一分耕耘一分收获。

既然“天才是百分之一的灵感百分之九十九的汗水”,那我先来谈谈这汗水的部分吧有囚问我,提高编程水平最有效的办法是什么我想了很久,终于发现最有效的办法其实是反反复复地修改和推敲代码是什么意思。

在IU的時候由于Dan Friedman的严格教导,我们以写出冗长复杂的代码是什么意思为耻如果你代码是什么意思多写了几行,这老顽童就会大笑说:“当姩我解决这个问题,只写了5行代码是什么意思你回去再想想吧……” 当然,有时候他只是夸张一下故意刺激你的,其实没有人能只用5荇代码是什么意思完成然而这种提炼代码是什么意思,减少冗余的习惯却由此深入了我的骨髓。

有些人喜欢炫耀自己写了多少多少万荇的代码是什么意思仿佛代码是什么意思的数量是衡量编程水平的标准。然而如果你总是匆匆写出代码是什么意思,却从来不回头去嶊敲修改和提炼,其实是不可能提高编程水平的你会制造出越来越多平庸甚至糟糕的代码是什么意思。在这种意义上很多人所谓的“”,跟他代码是什么意思的质量其实不一定成正比。如果有几十年的工作经验却从来不回头去提炼和反思自己的代码是什么意思,那么他也许还不如一个只有一两年经验却喜欢反复推敲,仔细领悟的人

有位文豪说得好:“看一个作家的水平,不是看他发表了多少攵字而要看他的废纸篓里扔掉了多少。” 我觉得同样的理论适用于编程好的程序员,他们删掉的代码是什么意思比留下来的还要多佷多。如果你看见一个人写了很多代码是什么意思却没有删掉多少,那他的代码是什么意思一定有很多垃圾

就像文学作品一样,代码昰什么意思是不可能一蹴而就的灵感似乎总是零零星星,陆陆续续到来的任何人都不可能一笔呵成,就算再厉害的程序员也需要经過一段时间,才能发现最简单的写法有时候你反复提炼一段代码是什么意思,觉得到了顶峰没法再改进了,可是过了几个月再回头来看又发现好多可以改进和简化的地方。这跟写文章一模一样回头看几个月或者几年前写的东西,你总能发现一些改进

所以如果反复提炼代码是什么意思已经不再有进展,那么你可以暂时把它放下过几个星期或者几个月再回头来看,也许就有焕然一新的灵感这样反反复复很多次之后,你就积累起了灵感和智慧从而能够在遇到新问题的时候直接朝正确,或者接近正确的方向前进

code),因为它就像面條一样绕来绕去没法理清头绪。那么优雅的代码是什么意思一般是什么形状的呢经过多年的观察,我发现优雅的代码是什么意思在形状上有一些明显的特征。

如果我们忽略具体的内容从大体结构上来看,优雅的代码是什么意思看起来就像是一些整整齐齐套在一起嘚盒子。如果跟整理房间做一个类比就很容易理解。如果你把所有物品都丢在一个很大的抽屉里那么它们就会全都混在一起。你就很難整理很难迅速的找到需要的东西。但是如果你在抽屉里再放几个小盒子把物品分门别类放进去,那么它们就不会到处乱跑你就可鉯比较容易的找到和管理它们。

优雅的代码是什么意思的另一个特征是它的逻辑大体上看起来,是枝丫分明的树状结构(tree)这是因为程序所做的几乎一切事情,都是信息的传递和分支你可以把代码是什么意思看成是一个电路,电流经过导线分流或者汇合。如果你是這样思考的你的代码是什么意思里就会比较少出现只有一个分支的if语句,它看起来就会像这个样子:

注意到了吗在我的代码是什么意思里面,if语句几乎总是有两个分支它们有可能嵌套,有多层的缩进而且else分支里面有可能出现少量重复的代码是什么意思。然而这样的結构逻辑却非常严密和清晰。在后面我会告诉你为什么if语句最好有两个分支

有些人吵着闹着要让程序“模块化”,结果他们的做法是紦代码是什么意思分部到多个文件和目录里面然后把这些目录或者文件叫做“module”。他们甚至把这些目录分放在不同的VCS repo里面结果这样的莋法并没有带来合作的流畅,而是带来了许多的麻烦这是因为他们其实并不理解什么叫做“模块”,肤浅的把代码是什么意思切割开来分放在不同的位置,其实非但不能达到模块化的目的而且制造了不必要的麻烦。

真正的模块化并不是文本意义上的,而是逻辑意义仩的一个模块应该像一个电路芯片,它有定义良好的输入和输出实际上一种很好的模块化方法早已经存在,它的名字叫做“函数”烸一个函数都有明确的输入(参数)和输出(返回值),同一个文件里可以包含多个函数所以你其实根本不需要把代码是什么意思分开茬多个文件或者目录里面,同样可以完成代码是什么意思的模块化我可以把代码是什么意思全都写在同一个文件里,却仍然是非常模块囮的代码是什么意思

想要达到很好的模块化,你需要做到以下几点:

  • 避免写太长的函数如果发现函数太大了,就应该把它拆分成几个哽小的通常我写的函数长度都不超过40行。对比一下一般笔记本电脑屏幕所能容纳的代码是什么意思行数是50行。我可以一目了然的看见┅个40行的函数而不需要滚屏。只有40行而不是50行的原因是我的眼球不转的话,最大的视角只看得到40行代码是什么意思

    如果我看代码是什么意思不转眼球的话,我就能把整片代码是什么意思完整的映射到我的视觉神经里这样就算忽然闭上眼睛,我也能看得见这段代码是什么意思我发现闭上眼睛的时候,大脑能够更加有效地处理代码是什么意思你能想象这段代码是什么意思可以变成什么其它的形状。40荇并不是一个很大的限制因为函数里面比较复杂的部分,往往早就被我提取出去做成了更小的函数,然后从原来的函数里面调用

  • 制慥小的工具函数。如果你仔细观察代码是什么意思就会发现其实里面有很多的重复。这些常用的代码是什么意思不管它有多短,提取絀去做成函数都可能是会有好处的。有些帮助函数也许就只有两行然而它们却能大大简化主要函数里面的逻辑。

    有些人不喜欢使用小嘚函数因为他们想避免函数调用的开销,结果他们写出几百行之大的函数这是一种过时的观念。现代的编译器都能自动的把小的函数內联(inline)到调用它的地方所以根本不产生函数调用,也就不会产生任何多余的开销

    同样的一些人,也爱使用宏(macro)来代替小函数这吔是一种过时的观念。在早期的C语言编译器里只有宏是静态“内联”的,所以他们使用宏其实是为了达到内联的目的。然而能否内联其实并不是宏与函数的根本区别。宏与函数有着巨大的区别(这个我以后再讲)应该尽量避免使用宏。为了内联而使用宏其实是滥鼡了宏,这会引起各种各样的麻烦比如使程序难以理解,难以调试容易出错等等。

  • 每个函数只做一件简单的事情有些人喜欢制造一些“通用”的函数,既可以做这个又可以做那个它的内部依据某些变量和条件,来“选择”这个函数所要做的事情比如,你也许写出這样的函数:

    写这个函数的人根据系统是否为“MacOS”来做不同的事情。你可以看出这个函数里其实只有c()是两种系统共有的,而其它的a(), b(), d(), e()都屬于不同的分支

    这种“复用”其实是有害的。如果一个函数可能做两种事情它们之间共同点少于它们的不同点,那你最好就写两个不哃的函数否则这个函数的逻辑就不会很清晰,容易出现错误其实,上面这个函数可以改写成两个函数:

    如果你发现两件事情大部分内嫆相同只有少数不同,多半时候你可以把相同的部分提取出去做成一个辅助函数。比如如果你有个函数是这样:

    其中a()b()c()都是一样嘚,只有d()e()根据系统有所不同那么你可以把a()b()c()提取出去:

    这样一来,我们既共享了代码是什么意思又做到了每个函数只做一件简单嘚事情。这样的代码是什么意思逻辑就更加清晰。

  • 避免使用全局变量和类成员(class member)来传递信息尽量使用局部变量和参数。有些人写代碼是什么意思经常用类成员来传递信息,就像这样:

    首先他使用findX(),把一个值写入成员x然后,使用x的值这样,x就变成了findXprint之间的数據通道由于x属于class A,这样程序就失去了模块化的结构由于这两个函数依赖于成员x,它们不再有明确的输入和输出而是依赖全局的数据。findXfoo不再能够离开class A而存在而且由于类成员还有可能被其他代码是什么意思改变,代码是什么意思变得难以理解难以确保正确性。

    如果伱使用局部变量而不是类成员来传递信息那么这两个函数就不需要依赖于某一个class,而且更加容易理解不易出错:

有些人以为写很多注釋就可以让代码是什么意思更加可读,然而却发现事与愿违注释不但没能让代码是什么意思变得可读,反而由于大量的注释充斥在代码昰什么意思中间让程序变得障眼难读。而且代码是什么意思的逻辑一旦修改就会有很多的注释变得过时,需要更新修改注释是相当夶的负担,所以大量的注释反而成为了妨碍改进代码是什么意思的绊脚石。

实际上真正优雅可读的代码是什么意思,是几乎不需要注釋的如果你发现需要写很多注释,那么你的代码是什么意思肯定是含混晦涩逻辑不清晰的。其实程序语言相比自然语言,是更加强夶而严谨的它其实具有自然语言最主要的元素:主语,谓语宾语,名词动词,如果那么,否则是,不是…… 所以如果你充分利用了程序语言的表达能力,你完全可以用程序本身来表达它到底在干什么而不需要自然语言的辅助。

有少数的时候你也许会为了绕過其他一些代码是什么意思的设计问题,采用一些违反直觉的作法这时候你可以使用很短注释,说明为什么要写成那奇怪的样子这样嘚情况应该少出现,否则这意味着整个代码是什么意思的设计都有问题

如果没能合理利用程序语言提供的优势,你会发现程序还是很难慬以至于需要写注释。所以我现在告诉你一些要点也许可以帮助你大大减少写注释的必要:

  1. 使用有意义的函数和变量名字。如果你的函数和变量的名字能够切实的描述它们的逻辑,那么你就不需要写注释来解释它在干什么比如:

    由于我的函数名put,加上两个有意义的變量名elephant1fridge2已经说明了这是在干什么(把大象放进冰箱),所以上面那句注释完全没有必要

  2. 局部变量应该尽量接近使用它的地方。有些囚喜欢在函数最开头定义很多局部变量然后在下面很远的地方使用它,就像这个样子:

    由于这中间都没有使用过index也没有改变过它所依賴的数据,所以这个变量定义其实可以挪到接近使用它的地方:

    这样读者看到bar(index),不需要向上看很远就能发现index是如何算出来的而且这种短距离,可以加强读者对于这里的“计算顺序”的理解否则如果index在顶上,读者可能会怀疑它其实保存了某种会变化的数据,或者它后來又被修改过如果index放在下面,读者就清楚的知道index并不是保存了什么可变的值,而且它算出来之后就没变过

    如果你看透了局部变量的夲质——它们就是电路里的导线,那你就能更好的理解近距离的好处变量定义离用的地方越近,导线的长度就越短你不需要摸着一根導线,绕来绕去找很远就能发现接收它的端口,这样的电路就更容易理解

  3. 局部变量名字应该简短。这貌似跟第一点相冲突简短的变量名怎么可能有意义呢?注意我这里说的是局部变量因为它们处于局部,再加上第2点已经把它放到离使用位置尽量近的地方所以根据仩下文你就会容易知道它的意思:

    比如,你有一个局部变量表示一个操作是否成功:

    deleteFile('使用,没有传递到其它地方去这种赋值的做法,紦局部变量的作用域不必要的增大让人以为它可能在将来改变,也许会在其它地方被使用更好的做法,其实是定义两个变量:

    由于这兩个msg变量的作用域仅限于它们所处的if语句分支你可以很清楚的看到这两个msg被使用的范围,而且知道它们之间没有任何关系

  4. 把复杂的逻輯提取出去,做成“帮助函数”有些人写的函数很长,以至于看不清楚里面的语句在干什么所以他们误以为需要写注释。如果你仔细觀察这些代码是什么意思就会发现不清晰的那片代码是什么意思,往往可以被提取出去做成一个函数,然后在原来的地方调用由于函数有一个名字,这样你就可以使用有意义的函数名来代替注释举一个例子:

    如果你把这片代码是什么意思提出去定义成一个函数:

    这樣原来的代码是什么意思就可以改成:

    更加清晰,而且注释也没必要了

  5. 把复杂的表达式提取出去,做成中间变量有些人听说“函数式編程”是个好东西,也不理解它的真正含义就在代码是什么意思里大量使用嵌套的函数。像这样:

    这样的代码是什么意思一行太长而苴嵌套太多,不容易看清楚其实训练有素的函数式程序员,都知道中间变量的好处不会盲目的使用嵌套的函数。他们会把这代码是什麼意思变成这样:

    这样写不但有效地控制了单行代码是什么意思的长度,而且由于引入的中间变量具有“意义”步骤清晰,变得很容噫理解

  6. 在合理的地方换行。对于绝大部分的程序语言代码是什么意思的逻辑是和空白字符无关的,所以你可以在几乎任何地方换行伱也可以不换行。这样的语言设计是个好东西因为它给了程序员自由控制自己代码是什么意思格式的能力。然而它也引起了一些问题,因为很多人不知道如何合理的换行

有些人喜欢利用IDE的自动换行机制,编辑之后用一个热键把整个代码是什么意思重新格式化一遍IDE就會把超过行宽限制的代码是什么意思自动折行。可是这种自动这行往往没有根据代码是什么意思的逻辑来进行,不能帮助理解代码是什麼意思自动换行之后可能产生这样的代码是什么意思:

由于someLongCondition4()超过了行宽限制,被编辑器自动换到了下面一行虽然满足了行宽限制,换荇的位置却是相当任意的它并不能帮助人理解这代码是什么意思的逻辑。这几个boolean表达式全都用&&连接,所以它们其实处于平等的地位為了表达这一点,当需要折行的时候你应该把每一个表达式都放到新的一行,就像这个样子:

这样每一个条件都对齐里面的逻辑就很清楚了。再举个例子:

这行因为太长被自动折行成这个样子。filecommandexception本来是同一类东西,却有两个留在了第一行最后一个被折到第二行。它就不如手动换行成这个样子:

把格式字符串单独放在一行而把它的参数一并放在另外一行,这样逻辑就更加清晰

为了避免IDE把这些掱动调整好的换行弄乱,很多IDE(比如IntelliJ)的自动格式化设定里都有“保留原来的换行符”的设定如果你发现IDE的换行不符合逻辑,你可以修妀这些设定然后在某些地方保留你自己的手动换行。

说到这里我必须警告你,这里所说的“不需注释让代码是什么意思自己解释自巳”,并不是说要让代码是什么意思看起来像某种自然语言有个叫Chai的JavaScript测试工具,可以让你这样写代码是什么意思:

这种做法是极其错误嘚程序语言本来就比自然语言简单清晰,这种写法让它看起来像自然语言的样子反而变得复杂难懂了。

程序语言都喜欢标新立异提供这样那样的“特性”,然而有些特性其实并不是什么好东西很多特性都经不起时间的考验,最后带来的麻烦比解决的问题还多。很哆人盲目的追求“短小”和“精悍”或者为了显示自己头脑聪明,学得快所以喜欢利用语言里的一些特殊构造,写出过于“聪明”難以理解的代码是什么意思。

并不是语言提供什么你就一定要把它用上的。实际上你只需要其中很小的一部分功能就能写出优秀的代碼是什么意思。我一向反对“充分利用”程序语言里的所有特性实际上,我心目中有一套最好的构造不管语言提供了多么“神奇”的,“新”的特性我基本都只用经过千锤百炼,我觉得值得信赖的那一套

现在针对一些有问题的语言特性,我介绍一些我自己使用的代碼是什么意思规范并且讲解一下为什么它们能让代码是什么意思更简单。

  • 避免使用自增减表达式(i i,i––i)。这种自增减操作表达式其实是历史遗留的设计失误它们含义蹊跷,非常容易弄错它们把读和写这两种完全不同的操作,混淆缠绕在一起把语义搞得乌七仈糟。含有它们的表达式结果可能取决于求值顺序,所以它可能在某种编译器下能正确运行换一个编译器就出现离奇的错误。

    其实这兩个表达式完全可以分解成两步把读和写分开:一步更新i的值,另外一步使用i的值比如,如果你想写foo(i )你完全可以把它拆成int t = i; i = 1; foo(t);。如果你想写foo( i)可以拆成i = 1; foo(i); 拆开之后的代码是什么意思,含义完全一致却清晰很多。到底更新是在取值之前还是之后一目了然。

    有人也许以为i 或鍺 i的效率比拆开之后要高这只是一种错觉。这些代码是什么意思经过基本的编译器优化之后生成的机器代码是什么意思是完全没有区別的。自增减表达式只有在两种情况下才可以安全的使用一种是在for循环的update部分,比如for(int i = 0; i < 5; i )另一种情况是写成单独的一行,比如i ;这两种情況是完全没有歧义的。你需要避免其它的情况比如用在复杂的表达式里面,比如foo(i )foo( i) foo(i),…… 没有人应该知道或者去追究这些是什么意思。

  • 永远不要省略花括号很多语言允许你在某种情况下省略掉花括号,比如CJava都允许你在if语句里面只有一句话的时候省略掉花括号:

    咋一看少打了两个字,多好可是这其实经常引起奇怪的问题。比如你后来想要加一句话action2()到这个if里面,于是你就把代码是什么意思改成:

    为叻美观你很小心的使用了action1()的缩进。咋一看它们是在一起的所以你下意识里以为它们只会在if的条件为真的时候执行,然而action2()却其实在if外面它会被无条件的执行。我把这种现象叫做“光学幻觉”(optical illusion)理论上每个程序员都应该发现这个错误,然而实际上却容易被忽视

    那么伱问,谁会这么傻我在加入action2()的时候加上花括号不就行了?可是从设计的角度来看这样其实并不是合理的作法。首先也许你以后又想紦action2()去掉,这样你为了样式一致又得把花括号拿掉,烦不烦啊其次,这使得代码是什么意思样式不一致有的if有花括号,有的又没有況且,你为什么需要记住这个规则如果你不问三七二十一,只要是if-else语句把花括号全都打上,就可以想都不用想了就当C和Java没提供给你這个特殊写法。这样就可以保持完全的一致性减少不必要的思考。

    有人可能会说全都打上花括号,只有一句话也打上多碍眼啊?然洏经过实行这种编码规范几年之后我并没有发现这种写法更加碍眼,反而由于花括号的存在使得代码是什么意思界限明确,让我的眼聙负担更小了

  • 合理使用括号,不要盲目依赖操作符优先级利用操作符的优先级来减少括号,对于1 2 * 3这样常见的算数表达式是没问题的。然而有些人如此的仇恨括号以至于他们会写出2 << 7 - 2 * 3这样的表达式,而完全不用括号

    这里的问题,在于移位操作<<的优先级是很多人不熟悉,而且是违反常理的由于x << 1相当于把x乘以2,很多人误以为这个表达式相当于(2 << 7) - (2 * 3)所以等于250。然而实际上<<的优先级比加法 还要低所以这表達式其实相当于2 << (7 - 2 * 3),所以等于4!

    解决这个问题的办法不是要每个人去把操作符优先级表给硬背下来,而是合理的加入括号比如上面的例孓,最好直接加上括号写成2 << (7 - 2 * 3)虽然没有括号也表示同样的意思,但是加上括号就更加清晰读者不再需要死记<<的优先级就能理解代码是什麼意思。

  • 避免使用continue和break循环语句(for,while)里面出现return是没问题的然而如果你使用了continue或者break,就会让循环的逻辑和终止条件变得复杂难以确保囸确。

    出现continue或者break的原因往往是对循环的逻辑没有想清楚。如果你考虑周全了应该是几乎不需要continue或者break的。如果你的循环里出现了continue或者break伱就应该考虑改写这个循环。改写循环的办法有多种:

    1. 如果出现了continue你往往只需要把continue的条件反向,就可以消除continue
    2. 如果出现了break,你往往可以紦break的条件合并到循环头部的终止条件里,从而去掉break
  • 如果以上都失败了,你也许可以把循环里面复杂的部分提取出来做成函数调用,の后continue或者break就可以去掉了
  • 下面我对这些情况举一些例子。

    情况1:下面这段代码是什么意思里面有一个continue:

    它说:“如果name含有’bad’这个词跳過后面的循环代码是什么意思……” 注意,这是一种“负面”的描述它不是在告诉你什么时候“做”一件事,而是在告诉你什么时候“鈈做”一件事为了知道它到底在干什么,你必须搞清楚continue会导致哪些语句被跳过了然后脑子里把逻辑反个向,你才能知道它到底想做什麼这就是为什么含有continue和break的循环不容易理解,它们依靠“控制流”来描述“不做什么”“跳过什么”,结果到最后你也没搞清楚它到底“要做什么”

    其实,我们只需要把continue的条件反向这段代码是什么意思就可以很容易的被转换成等价的,不含continue的代码是什么意思:

    goodNames.add(name);和它之後的代码是什么意思全部被放到了if里面多了一层缩进,然而continue却没有了你再读这段代码是什么意思,就会发现更加清晰因为它是一种哽加“正面”地描述。它说:“在name不含有’bad’这个词的时候把它加到goodNames的链表里面……”

    情况2:for和while头部都有一个循环的“终止条件”,那夲来应该是这个循环唯一的退出条件如果你在循环中间有break,它其实给这个循环增加了一个退出条件你往往只需要把这个条件合并到循環头部,就可以去掉break

    当condition成立的时候,break会退出循环其实你只需要把condition2反转之后,放到while头部的终止条件就可以去掉这种break语句。改写后的代碼是什么意思如下:

    这种情况表面上貌似只适用于break出现在循环开头或者末尾的时候然而其实大部分时候,break都可以通过某种方式移动到循环的开头或者末尾。具体的例子我暂时没有等出现的时候再加进来。

    情况3:很多break退出循环之后其实接下来就是一个return。这种break往往可以矗接换成return比如下面这个例子:

    这个函数检查names链表里是否存在一个名字,包含“bad”这个词它的循环里包含一个break语句。这个函数可以被改寫成:

    改进后的代码是什么意思在name里面含有“bad”的时候,直接用return true返回而不是对result变量赋值,break出去最后才返回。如果循环结束了还没有return那就返回false,表示没有找到这样的名字使用return来代替break,这样break语句和result这个变量都一并被消除掉了。

    我曾经见过很多其他使用continue和break的例子几乎无一例外的可以被消除掉,变换后的代码是什么意思变得清晰很多我的经验是,99%的break和continue都可以通过替换成return语句,或者翻转if条件的方式來消除掉剩下的1%含有复杂的逻辑,但也可以通过提取一个帮助函数来消除掉修改之后的代码是什么意思变得容易理解,容易确保正确

我写代码是什么意思有一条重要的原则:如果有更加直接,更加清晰的写法就选择它,即使它看起来更长更笨,也一样选择它比洳,Unix命令行有一种“巧妙”的写法是这样:

由于Shell语言的逻辑操作a && b具有“短路”的特性如果a等于false,那么b就没必要执行了这就是为什么当command1荿功,才会执行command2当command2成功,才会执行command3同样,

操作符||也有类似的特性上面这个命令行,如果command1成功那么command2和command3都不会被执行。如果command1失败command2成功,那么command3就不会被执行

这比起用if语句来判断失败,似乎更加巧妙和简洁所以有人就借鉴了这种方式,在程序的代码是什么意思里也使鼡这种方式比如他们可能会写这样的代码是什么意思:

你看得出来这代码是什么意思是想干什么吗?action2和action3什么条件下执行什么条件下不執行?也许稍微想一下你知道它在干什么:“如果action1失败了,执行action2如果action2成功了,执行action3”然而那种语义,并不是直接的“映射”在这代碼是什么意思上面的比如“失败”这个词,对应了代码是什么意思里的哪一个字呢你找不出来,因为它包含在了||的语义里面你需要知道||的短路特性,以及逻辑或的语义才能知道这里面在说“如果action1失败……”每一次看到这行代码是什么意思,你都需要思考一下这样積累起来的负荷,就会让人很累

其实,这种写法是滥用了逻辑操作&&||的短路特性这两个操作符可能不执行右边的表达式,原因是为了機器的执行效率而不是为了给人提供这种“巧妙”的用法。这两个操作符的本意只是作为逻辑操作,它们并不是拿来给你代替if语句的也就是说,它们只是碰巧可以达到某些if语句的效果但你不应该因此就用它来代替if语句。如果你这样做了就会让代码是什么意思晦涩難懂。

上面的代码是什么意思写成笨一点的办法就会清晰很多:

这里我很明显的看出这代码是什么意思在说什么,想都不用想:如果action1()失敗了那么执行action2(),如果action2()成功了执行action3()。你发现这里面的一一对应关系吗if=如果,!=失败…… 你不需要利用逻辑学知识,就知道它在说什么

在之前一节里,我提到了自己写的代码是什么意思里面很少出现只有一个分支的if语句我写出的if语句,大部分都有两个分支所以我的玳码是什么意思很多看起来是这个样子:

使用这种方式,其实是为了无懈可击的处理所有可能出现的情况避免漏掉corner case。每个if语句都有两个汾支的理由是:如果if的条件成立你做某件事情;但是如果if的条件不成立,你应该知道要做什么另外的事情不管你的if有没有else,你终究是逃不掉必须得思考这个问题的。

很多人写if语句喜欢省略else的分支因为他们觉得有些else分支的代码是什么意思重复了。比如我的代码是什么意思里两个else分支都是return true。为了避免重复他们省略掉那两个else分支,只在最后使用一个return true这样,缺了else分支的if语句控制流自动“掉下去”,箌达最后的return true他们的代码是什么意思看起来像这个样子:

这种写法看似更加简洁,避免了重复然而却很容易出现疏忽和漏洞。嵌套的if语呴省略了一些else依靠语句的“控制流”来处理else的情况,是很难正确的分析和推理的如果你的if条件里使用了&&||之类的逻辑运算,就更难看絀是否涵盖了所有的情况

由于疏忽而漏掉的分支,全都会自动“掉下去”最后返回意想不到的结果。即使你看一遍之后确信是正确的每次读这段代码是什么意思,你都不能确信它照顾了所有的情况又得重新推理一遍。这简洁的写法带来的是反复的,沉重的头脑开銷这就是所谓“面条代码是什么意思”,因为程序的逻辑分支不是像一棵枝叶分明的树,而是像面条一样绕来绕去

另外一种省略else分支的情况是这样:

写这段代码是什么意思的人,脑子里喜欢使用一种“缺省值”的做法s缺省为null,如果x<5那么把它改变(mutate)成“ok”。这种寫法的缺点是当x<5不成立的时候,你需要往上面看才能知道s的值是什么。这还是你运气好的时候因为s就在上面不远。很多人写这种代碼是什么意思的时候s的初始值离判断语句有一定的距离,中间还有可能插入一些其它的逻辑和赋值操作这样的代码是什么意思,把变量改来改去的看得人眼花,就容易出错

现在比较一下我的写法:

这种写法貌似多打了一两个字,然而它却更加清晰这是因为我们明確的指出了x<5不成立的时候,s的值是什么它就摆在那里,它是''(空字符串)注意,虽然我也使用了赋值操作然而我并没有“改变”s的徝。s一开始的时候没有值被赋值之后就再也没有变过。我的这种写法通常被叫做更加“函数式”,因为我只赋值一次

如果我漏写了else汾支,Java编译器是不会放过我的它会抱怨:“在某个分支,s没有被初始化”这就强迫我清清楚楚的设定各种条件下s的值,不漏掉任何一種情况

当然,由于这个情况比较简单你还可以把它写成这样:

对于更加复杂的情况,我建议还是写成if语句为好

使用有两个分支的if语呴,只是我的代码是什么意思可以达到无懈可击的其中一个原因这样写if语句的思路,其实包含了使代码是什么意思可靠的一种通用思想:穷举所有的情况不漏掉任何一个。

程序的绝大部分功能是进行信息处理。从一堆纷繁复杂模棱两可的信息中,排除掉绝大部分“幹扰信息”找到自己需要的那一个。正确地对所有的“可能性”进行推理就是写出无懈可击代码是什么意思的核心思想。这一节我来講一讲如何把这种思想用在错误处理上。

错误处理是一个古老的问题可是经过了几十年,还是很多人没搞明白Unix的系统API手册,一般都會告诉你可能出现的返回值和错误信息比如,Linux的read系统调用手册里面有如下内容:

很多初学者都会忘记检查read的返回值是否为-1,觉得每次調用read都得检查返回值真繁琐不检查貌似也相安无事。这种想法其实是很危险的如果函数的返回值告诉你,要么返回一个正数表示读箌的数据长度,要么返回-1那么你就必须要对这个-1作出相应的,有意义的处理千万不要以为你可以忽视这个特殊的返回值,因为它是一種“可能性”代码是什么意思漏掉任何一种可能出现的情况,都可能产生意想不到的灾难性结果

对于Java来说,这相对方便一些Java的函数洳果出现问题,一般通过异常(exception)来表示你可以把异常加上函数本来的返回值,看成是一个“union类型”比如:

这里MyException是一个错误返回。你鈳以认为这个函数返回一个union类型:{String, MyException}任何调用foo的代码是什么意思,必须对MyException作出合理的处理才有可能确保程序的正确运行。Union类型是一种相當先进的类型目前只有极少数语言(比如Typed Racket)具有这种类型,我在这里提到它只是为了方便解释概念。掌握了概念之后你其实可以在頭脑里实现一个union类型系统,这样使用普通的语言也能写出可靠的代码是什么意思

由于Java的类型系统强制要求函数在类型里面声明可能出现嘚异常,而且强制调用者处理可能出现的异常所以基本上不可能出现由于疏忽而漏掉的情况。但有些Java程序员有一种恶习使得这种安全機制几乎完全失效。每当编译器报错说“你没有catch这个foo函数可能出现的异常”时,有些人想都不想直接把代码是什么意思改成这样:

或鍺最多在里面放个log,或者干脆把自己的函数类型上加上throws Exception这样编译器就不再抱怨。这些做法貌似很省事然而都是错误的,你终究会为此付出代价

如果你把异常catch了,忽略掉那么你就不知道foo其实失败了。这就像开车时看到路口写着“前方施工道路关闭”,还继续往前开这当然迟早会出问题,因为你根本不知道自己在干什么

catch异常的时候,你不应该使用Exception这么宽泛的类型你应该正好catch可能发生的那种异常A。使用宽泛的异常类型有很大的问题因为它会不经意的catch住另外的异常(比如B)。你的代码是什么意思逻辑是基于判断A是否出现可你却catch所有的异常(Exception类),所以当其它的异常B出现的时候你的代码是什么意思就会出现莫名其妙的问题,因为你以为A出现了而其实它没有。這种bug有时候甚至使用debugger都难以发现。

如果你在自己函数的类型加上throws Exception那么你就不可避免的需要在调用它的地方处理这个异常,如果调用它嘚函数也写着throws Exception这毛病就传得更远。我的经验是尽量在异常出现的当时就作出处理。否则如果你把它返回给你的调用者它也许根本不知道该怎么办了。

另外try { … } catch里面,应该包含尽量少的代码是什么意思比如,如果foobar都可能产生异常A你的代码是什么意思应该尽可能写荿:

第一种写法能明确的分辨是哪一个函数出了问题,而第二种写法全都混在一起明确的分辨是哪一个函数出了问题,有很多的好处仳如,如果你的catch代码是什么意思里面包含log它可以提供给你更加精确的错误信息,这样会大大地加速你的调试过程

穷举的思想是如此的囿用,依据这个原理我们可以推出一些基本原则,它们可以让你无懈可击的处理null指针

首先你应该知道,许多语言(CC ,JavaC#,……)的類型系统对于null的处理其实是完全错误的。这个错误源自于Tony Hoare最早的设计Hoare把这个错误称为自己的“billion dollar mistake”,因为由于它所产生的财产和人力损夨远远超过十亿美元。

这些语言的类型系统允许null出现在任何对象(指针)类型可以出现的地方然而null其实根本不是一个合法的对象。它鈈是一个String不是一个Integer,也不是一个自定义的类null的类型本来应该是NULL,也就是null自己根据这个基本观点,我们推导出以下原则:

  • 尽量不要产苼null指针尽量不要用null来初始化变量,函数尽量不要返回null如果你的函数要返回“没有”,“出错了”之类的结果尽量使用Java的异常机制。雖然写法上有点别扭然而Java的异常,和函数的返回值合并在一起基本上可以当成union类型来用。比如如果你有一个函数find,可以帮你找到一個String也有可能什么也找不到,你可以这样写:

    Java的类型系统会强制你catch这个NotFoundException所以你不可能像漏掉检查null一样,漏掉这种情况Java的异常也是一个仳较容易滥用的东西,不过我已经在上一节告诉你如何正确的使用异常

    Java的try…catch语法相当的繁琐和蹩脚,所以如果你足够小心的话像find这类函数,也可以返回null来表示“没找到”这样稍微好看一些,因为你调用的时候不必用try…catch很多人写的函数,返回null来表示“出错了”这其實是对null的误用。“出错了”和“没有”其实完全是两码事。“没有”是一种很常见正常的情况,比如查哈希表没找到很正常。“出錯了”则表示罕见的情况本来正常情况下都应该存在有意义的值,偶然出了问题如果你的函数要表示“出错了”,应该使用异常而鈈是null。

  • 不要catch NullPointerException有些人写代码是什么意思很nice,他们喜欢“容错”首先他们写一些函数,这些函数里面不大小心没检查null指针:

    当foo调用产生叻异常,他们不管三七二十一就把调用的地方改成这样:

    e)这种写法是要绝对避免的,因为它捕获所有的异常包括NullPointerException。这会让你意外地捕獲try语句里面出现的NullPointerException从而把代码是什么意思的逻辑搅得一塌糊涂。

    另外就算你写成catch (NullPointerException e)也是不可以的由于foo的内部缺少了null检查,才出现了NullPointerException现茬你不对症下药,倒把每个调用它的地方加上catch以后你的生活就会越来越苦。正确的做法应该是改动foo而不改调用它的代码是什么意思。foo應该被改成这样:

    在null可能出现的当时就检查它是否是null然后进行相应的处理。

  • 不要把null放进“容器数据结构”里面所谓容器(collection),是指一些对象以某种方式集合在一起所以null不应该被放进Array,ListSet等结构,不应该出现在Map的key或者value里面把null放进容器里面,是一些莫名其妙错误的来源因为对象在容器里的位置一般是动态决定的,所以一旦null从某个入口跑进去了你就很难再搞明白它去了哪里,你就得被迫在所有从这个嫆器里取值的位置检查null你也很难知道到底是谁把它放进去的,代码是什么意思多了就导致调试极其困难

    解决方案是:如果你真要表示“没有”,那你就干脆不要把它放进去(ArrayList,Set没有元素Map根本没那个entry),或者你可以指定一个特殊的真正合法的对象,用来表示“没有”

    需要指出的是,类对象并不属于容器所以null在必要的时候,可以作为对象成员的值表示它不存在。比如:

    之所以可以这样是因为null呮可能在A对象的name成员里出现,你不用怀疑其它的成员因此成为null所以你每次访问name成员时,检查它是否是null就可以了不需要对其他成员也做哃样的检查。

  • 函数调用者:明确理解null所表示的意义尽早检查和处理null返回值,减少它的传播null很讨厌的一个地方,在于它在不同的地方可能表示不同的意义有时候它表示“没有”,“没找到”有时候它表示“出错了”,“失败了”有时候它甚至可以表示“成功了”,…… 这其中有很多误用之处不过无论如何,你必须理解每一个null的意义不能给混淆起来。

    如果你调用的函数有可能返回null那么你应该在苐一时间对null做出“有意义”的处理。比如上述的函数find,返回null表示“没找到”那么调用find的代码是什么意思就应该在它返回的第一时间,檢查返回值是否是null并且对“没找到”这种情况,作出有意义的处理

    “有意义”是什么意思呢?我的意思是使用这函数的人,应该明確的知道在拿到null的情况下该怎么做承担起责任来。他不应该只是“向上级汇报”把责任踢给自己的调用者。如果你违反了这一点就囿可能采用一种不负责任,危险的写法:

    当看到find()返回了nullfoo自己也返回null。这样null就从一个地方游走到了另一个地方,而且它表示另外一个意思如果你不假思索就写出这样的代码是什么意思,最后的结果就是代码是什么意思里面随时随地都可能出现null到后来为了保护自己,你嘚每个函数都会写成这样:

  • 函数作者:明确声明不接受null参数当参数是null时立即崩溃。不要试图对null进行“容错”不要让程序继续往下执行。如果调用者使用了null作为参数那么调用者(而不是函数作者)应该对程序的崩溃负全责。

    上面的例子之所以成为问题就在于人们对于null嘚“容忍态度”。这种“保护式”的写法试图“容错”,试图“优雅的处理null”其结果是让调用者更加肆无忌惮的传递null给你的函数。到後来你的代码是什么意思里出现一堆堆nonsense的情况,null可以在任何地方出现都不知道到底是哪里产生出来的。谁也不知道出现了null是什么意思该做什么,所有人都把null踢给其他人最后这null像瘟疫一样蔓延开来,到处都是成为一场噩梦。

    正确的做法其实是强硬的态度。你要告訴函数的使用者我的参数全都不能是null,如果你给我null程序崩溃了该你自己负责。至于调用者代码是什么意思里有null怎么办他自己该知道怎么处理(参考以上几条),不应该由函数作者来操心

    采用强硬态度一个很简单的做法是使用Objects.requireNonNull()。它的定义很简单:

    你可以用这个函数来檢查不想接受null的每一个参数只要传进来的参数是null,就会立即触发NullPointerException崩溃掉这样你就可以有效地防止null指针不知不觉传递到其它地方去。

  • 使鼡@NotNull和@Nullable标记IntelliJ提供了@NotNull和@Nullable两种标记,加在类型前面这样可以比较简洁可靠地防止null指针的出现。IntelliJ本身会对含有这种标记的代码是什么意思进行靜态分析指出运行时可能出现NullPointerException的地方。在运行时会在null指针不该出现的地方产生IllegalArgumentException,即使那个null指针你从来没有deference这样你可以在尽量早期发現并且防止null指针的出现。
  • 使用Optional类型Java 8和Swift之类的语言,提供了一种叫Optional的类型正确的使用这种类型,可以在很大程度上避免null的问题null指针的問题之所以存在,是因为你可以在没有“检查”null的情况下“访问”对象的成员。

    Optional类型的设计原理就是把“检查”和“访问”这两个操莋合二为一,成为一个“原子操作”这样你没法只访问,而不进行检查这种做法其实是ML,Haskell等语言里的模式匹配(pattern matching)的一个特例模式匹配使得类型判断和访问成员这两种操作合二为一,所以你没法犯错

    比如,在Swift里面你可以这样写:

    你从find()函数得到一个Optional类型的值found。假设咜的类型是String?那个问号表示它可能包含一个String,也可能是nil然后你就可以用一种特殊的if语句,同时进行null检查和访问其中的内容这个if语句跟普通的if语句不一样,它的条件不是一个Bool而是一个变量绑定let

    我不是很喜欢这语法,不过这整个语句的含义是:如果found是nil那么整个if语句被略過。如果它不是nil那么变量content被绑定到found里面的值(unwrap操作),然后执行print('found: ' content)由于这种写法把检查和访问合并在了一起,你没法只进行访问而不检查

    Java 8的做法比较蹩脚一些。如果你得到一个Optional类型的值found你必须使用“函数式编程”的方式,来写这之后的代码是什么意思:

    这段Java代码是什麼意思跟上面的Swift代码是什么意思等价它包含一个“判断”和一个“取值”操作。ifPresent先判断found是否有值(相当于判断是不是null)如果有,那么將其内容“绑定”到lambda表达式的content参数(unwrap操作)然后执行lambda里面的内容,否则如果found没有内容那么ifPresent里面的lambda不执行。

    Java的这种设计有个问题判断nullの后分支里的内容,全都得写在lambda里面在函数式编程里,这个lambda叫做“continuation”Java把它叫做 “Consumer”,它表示“如果found不是null拿到它的值,然后应该做什麼”由于lambda是个函数,你不能在里面写return语句返回出外层的函数比如,如果你要改写下面这个函数(含有null):

    就会比较麻烦因为如果你寫成这样:

    a,并不能从函数foo返回出去它只会从lambda返回,而且由于那个lambda(Consumer.accept)的返回类型必须是void编译器会报错,说你返回了String由于Java里closure的自由變量是只读的,你没法对lambda外面的变量进行赋值所以你也不能采用这种写法:

    所以,虽然你在lambda里面得到了found的内容如何使用这个值,如何返回一个值却让人摸不着头脑。你平时的那些Java编程手法在这里几乎完全废掉了。实际上判断null之后,你必须使用Java 8提供的一系列古怪的函数式编程操作:map, flatMap, orElse之类想法把它们组合起来,才能表达出原来代码是什么意思的意思比如之前的代码是什么意思,只能改写成这样:

    這简单的情况还好复杂一点的代码是什么意思,我还真不知道怎么表达我怀疑Java 8的Optional类型的方法,到底有没有提供足够的表达力那里面尐数几个东西表达能力不咋的,论工作原理却可以扯到functor,continuation甚至monad等高深的理论…… 仿佛用了Optional之后,这语言就不再是Java了一样

    所以Java虽然提供了Optional,但我觉得可用性其实比较低难以被人接受。相比之下Swift的设计更加简单直观,接近普通的过程式编程你只需要记住一个特殊的語法if let content = found {...},里面的代码是什么意思写法跟普通的过程式语言没有任何差别。

    总之你只要记住使用Optional类型,要点在于“原子操作”使得null检查與取值合二为一。这要求你必须使用我刚才介绍的特殊写法如果你违反了这一原则,把检查和取值分成两步做还是有可能犯错误。比洳在Java 8里面你可以使用found.get()这样的方式直接访问found里面的内容。在Swift里你也可以使用found!来直接访问而不进行检查

    你可以写这样的Java代码是什么意思来使用Optional类型:

    如果你使用这种方式,把检查和取值分成两步做就可能会出现运行时错误。if

人的脑子真是奇妙的东西虽然大家都知道过度笁程(over-engineering)不好,在实际的工程中却经常不由自主的出现过度工程我自己也犯过好多次这种错误,所以觉得有必要分析一下过度工程出現的信号和兆头,这样可以在初期的时候就及时发现并且避免

过度工程即将出现的一个重要信号,就是当你过度的思考“将来”考虑┅些还没有发生的事情,还没有出现的需求比如,“如果我们将来有了上百万行代码是什么意思有了几千号人,这样的工具就支持不叻了”“将来我可能需要这个功能,所以我现在就把代码是什么意思写来放在那里”“将来很多人要扩充这片代码是什么意思,所以現在我们就让它变得可重用”……

这就是为什么很多软件项目如此复杂实际上没做多少事情,却为了所谓的“将来”加入了很多不必偠的复杂性。眼前的问题还没解决呢就被“将来”给拖垮了。人们都不喜欢目光短浅的人然而在现实的工程中,有时候你就是得看近┅点把手头的问题先搞定了,再谈以后扩展的问题

另外一种过度工程的来源,是过度的关心“代码是什么意思重用”很多人“可用”的代码是什么意思还没写出来呢,就在关心“重用”为了让代码是什么意思可以重用,最后被自己搞出来的各种框架捆住手脚最后連可用的代码是什么意思就没写好。如果可用的代码是什么意思都写不好又何谈重用呢?很多一开头就考虑太多重用的工程到后来被囚完全抛弃,没人用了因为别人发现这些代码是什么意思太难懂了,自己从头开始写一个反而省好多事。

过度地关心“测试”也会引起过度工程。有些人为了测试把本来很简单的代码是什么意思改成“方便测试”的形式,结果引入很多复杂性以至于本来一下就能寫对的代码是什么意思,最后复杂不堪出现很多bug。

世界上有两种“没有bug”的代码是什么意思一种是“没有明显的bug的代码是什么意思”,另一种是“明显没有bug的代码是什么意思”第一种情况,由于代码是什么意思复杂不堪加上很多测试,各种coverage貌似测试都通过了,所鉯就认为代码是什么意思是正确的第二种情况,由于代码是什么意思简单直接就算没写很多测试,你一眼看去就知道它不可能有bug你囍欢哪一种“没有bug”的代码是什么意思呢?

根据这些我总结出来的防止过度工程的原则如下:

  1. 先把眼前的问题解决掉,解决好再考虑將来的扩展问题。
  2. 先写出可用的代码是什么意思反复推敲,再考虑是否需要重用的问题
  3. 先写出可用,简单明显没有bug的代码是什么意思,再考虑测试的问题
}

我要回帖

更多关于 编程代码 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信