导航资讯

主页 > 香港赛马会惠泽社群 >

香港赛马会惠泽社群

港彩唯一官方网站 loginfo

发布时间: 2019-06-16 点击数:
c?北京同仁医院眼角膜科副主任接英介绍,精准平特一肖公式,可以直接去看急诊,美国大型科技公司正试图不公开地抵制或淡化印度的新监管措施。新的限制肯定会出台。华为携手中国天津滨海新区与万科共同宣布最新合作,全面、长期、面向未来地驱动企业数字化转型升级。这时的女性虽然拥有了接受教育的权利。
出身富裕士族的塔子,”他说,”他说,意味着很多的特殊处理代码,到底何时使用检查反常,使学生滞留于外部行为技能的把握而忽略了能力的迁移和摸索的深入。而被理论界力推的一种教学取向。你回去再想想吧.." 当然有时候他只是夸张一下有意刺激你的其实没有人能只用 5 行代码完成然而这种提炼代码减少冗余的习惯却由此深入了我的骨髓有些人喜欢炫耀自己写了多少多少万行的代码似乎代码的数量是衡量编程水平的标准然而如果你总是匆匆写出代码却从来不回头去推敲修改和提炼其实是不可能提高编程水平的你会制造出越来越多无能甚至糟糕的代码在这种意义上很多人所谓的"工作体会"跟他代码的质量其实不一定成正比如果有几十年的工作经验却从来不回头去提炼和反思自己的代码那么他也许还不如一个只有一两年经验却喜欢反复推敲仔细领会的人有位文豪说得好:"看一个作家的水平不是看他发表了多少文字而要看他的废纸篓里扔掉了多少" 我觉得同样的理论适用于编程好的程序员他们删掉的代码比留下来的还要多很多如果你看见一个人写了很多代码却没有删掉多少那他的代码一定有很多垃圾就像文学作品一样代码是不可能一蹴而就的灵感似乎总是零零星星陆陆续续到来的任何人都不可能一笔呵成就算再厉害的程序员也需要经过一段时间才能发现最简单优雅的写法有时候你反复提炼一段代码觉得到了顶峰没法再改进了可是过了几个月再回头来看又发现好多可以改进和简化的地方这跟写文章一模一样回头看几个月或者几年前写的东西你总能发现一些改进所以如果反复提炼代码已经不再有进展那么你可以暂时把它放下过几个星期或者几个月再回头来看也许就有焕然一新的灵感这样反反复复很多次之后你就积存起了灵感和聪明从而能够在遇到新问题的时候直接朝正确或者接近正确的方向前进写优雅的代码人们都厌恶"面条代码"(spaghetti code)因为它就像面条一样绕来绕去没法理清头绪那么优雅的代码一般是什么形状的呢经过多年的观察我发现优雅的代码在形状上有一些明显的特征如果我们忽略具体的内容从大体结构上来看优雅的代码看起来就像是一些整整齐齐套在一起的盒子如果跟整理房间做一个类比就很容易理解如果你把所有物品都丢在一个很大的抽屉里那么它们就会全都混在一起你就很难整理很难迅速的找到需要的东西但是如果你在抽屉里再放几个小盒子把物品分门别类放进去那么它们就不会到处乱跑你就可以比较容易的找到和治理它们优雅的代码的另一个特点是它的逻辑大体上看起来是枝丫分明的树状结构(tree)这是因为程序所做的几乎一切事情都是信息的传递和分支你可以把代码看成是一个电路电流经过导线分流或者汇合如果你是这样思考的你的代码里就会比较少出现只有一个分支的 if 语句它看起来就会像这个样子:if (.) { if (.) { . } else { . }} else if (.) { .} else { .}注意到了吗在我的代码里面if 语句几乎总是有两个分支它们有可能嵌套有多层的缩进而且 else 分支里面有可能出现少量重复的代码然而这样的结构逻辑却非常严密和清晰在后面我会告诉你为什么 if 语句最好有两个分支写模块化的代码有些人吵着闹着要让程序"模块化"结果他们的做法是把代码分布到多个文件和目录里面然后把这些目录或者文件叫做"module"他们甚至把这些目录分放在不同的 VCS repo 里面结果这样的作法并没有带来合作的流畅而是带来了许多的麻烦这是因为他们其实并不理解什么叫做"模块"肤浅的把代码切割开来分放在不同的位置其实非但不能达到模块化的目的而且制造了不必要的麻烦真正的模块化并不是文本意义上的而是逻辑意义上的一个模块应该像一个电路芯片它有定义良好的输入和输出实际上一种很好的模块化方法早已经存在它的名字叫做"函数"每一个函数都有明确的输入(参数)和输出(返回值)同一个文件里可以包含多个函数所以你其实根本不需要把代码分开在多个文件或者目录里面同样可以完成代码的模块化我可以把代码全都写在同一个文件里却仍旧是非常模块化的代码想要达到很好的模块化你需要做到以下几点:避免写太长的函数如果发现函数太大了就应该把它拆分成几个更小的通常我写的函数长度都不超过 40 行对比一下一般笔记本电脑屏幕所能容纳的代码行数是 50 行我可以一目了然的看见一个 40 行的函数而不需要滚屏只有 40 行而不是 50 行的原因是我的眼球不转的话最大的视角只看得到 40 行代码如果我看代码不转眼球的话我就能把整片代码完整的映射到我的视觉神经里这样就算忽然闭上眼睛我也能看得见这段代码我发现闭上眼睛的时候大脑能够更加有效地处理代码你能想象这段代码可以变成什么其它的形状40 行并不是一个很大的限制因为函数里面比较复杂的部分往往早就被我提取出去做成了更小的函数然后从原来的函数里面调用制造小的工具函数如果你仔细观察代码就会发现其实里面有很多的重复这些常用的代码不管它有多短提取出去做成函数都可能是会有好处的有些帮助函数也许就只有两行然而它们却能大大简化主要函数里面的逻辑有些人不喜欢使用小的函数因为他们想避免函数调用的开销结果他们写出几百行之大的函数这是一种过时的观念现代的编译器都能自动的把小的函数内联(inline)到调用它的地方所以根本不产生函数调用也就不会产生任何余外的开销同样的一些人也爱使用宏(macro)来代替小函数这也是一种过时的观念在早期的C语言编译器里只有宏是静态"内联"的所以他们使用宏其实是为了达到内联的目的然而能否内联其实并不是宏与函数的根本区别宏与函数有着巨大的区别(这个我以后再讲)应该尽量避免使用宏为了内联而使用宏其实是滥用了宏这会引起各种各样的麻烦比如使程序难以理解难以调试容易出错等等每个函数只做一件简单的事情有些人喜欢制造一些"通用"的函数既可以做这个又可以做那个它的内部依据某些变量和条件来"挑选"这个函数所要做的事情比如你也许写出这样的函数:void foo () { if (getOS () equals ("MacOS")) { a (); } else { b (); } c (); if (getOS () equals ("MacOS")) { d (); } else { e (); }}写这个函数的人根据系统是否为"MacOS"来做不同的事情你可以看出这个函数里其实只有c()是两种系统共有的而其它的a()b()d()e()都属于不同的分支这种"复用"其实是有害的如果一个函数可能做两种事情它们之间共同点少于它们的不同点那你最好就写两个不同的函数否则这个函数的逻辑就不会很清晰容易出现错误其实上面这个函数可以改写成两个函数:void fooMacOS () { a (); c (); d ();}和void fooOther () { b (); c (); e ();}如果你发现两件事情大部分内容相同只有少数不同多半时候你可以把相同的部分提取出去做成一个辅助函数比如如果你有个函数是这样:void foo () { a (); b () c (); if (getOS () equals ("MacOS")) { d (); } else { e (); }}其中a()b()c()都是一样的只有d()和e()根据系统有所不同那么你可以把a()b()c()提取出去:void preFoo () { a (); b () c ();然后制造两个函数:void fooMacOS () { preFoo (); d ();}和void fooOther () { preFoo (); e ();}这样一来我们既共享了代码又做到了每个函数只做一件简单的事情这样的代码逻辑就更加清楚避免使用全局变量和类成员(class member)来传递信息尽量使用局部变量和参数有些人写代码经常用类成员来传递信息就像这样:class A { String x; void findX () { . x = .; } void foo () { findX (); . print (x); } }第一他使用findX()把一个值写入成员x然后使用x的值这样x就变成了findX和print之间的数据通道由于x属于class A这样程序就失去了模块化的结构由于这两个函数依靠于成员x它们不再有明确的输入和输出而是依赖全局的数据findX和foo不再能够离开class A而存在而且由于类成员还有可能被其他代码改变代码变得难以理解难以确保正确性如果你使用局部变量而不是类成员来传递信息那么这两个函数就不需要依赖于某一个 class而且更加容易理解不易出错: String findX () { . x = .; return x; } void foo () { int x = findX (); print (x); }写可读的代码有些人以为写很多注释就可以让代码更加可读然而却发现事与愿违注释不但没能让代码变得可读反而由于大量的注释充斥在代码中间让程序变得障眼难读而且代码的逻辑一旦修改就会有很多的注释变得过时需要更新修改注释是相当大的负担所以大量的注释反而成为了阻碍改进代码的绊脚石实际上真正优雅可读的代码是几乎不需要注释的如果你发现需要写很多注释那么你的代码肯定是含混晦涩逻辑不清晰的其实程序语言相比自然语言是更加强大而严谨的它其实具有自然语言最主要的元素:主语谓语宾语名词动词如果那么否则是不是.. 所以如果你充分利用了程序语言的表达能力你完全可以用程序本身来表达它到底在干什么而不需要自然语言的辅助有少数的时候你也许会为了绕过其他一些代码的设计问题采用一些违反直觉的作法这时候你可以使用很短注释说明为什么要写成那奇特的样子这样的情况应该少出现否则这意味着整个代码的设计都有问题如果没能合理利用程序语言提供的优势你会发现程序还是很难懂以至于需要写注释所以我现在告诉你一些要点也许可以帮助你大大减少写注释的必要:1、使用有意义的函数和变量名字如果你的函数和变量的名字能够切实的描述它们的逻辑那么你就不需要写注释来解释它在干什么比如:// put elephant1 into fridge2put (elephant1 fridge2);由于我的函数名put加上两个有意义的变量名elephant1和fridge2已经说明了这是在干什么(把大象放进冰箱)所以上面那句注释完全没有必要2、局部变量应该尽量接近使用它的地方有些人喜欢在函数最开头定义很多局部变量然后在下面很远的地方使用它就像这个样子:void foo () { int index = .; . ,开奖记录开奖结果. bar (index); .}由于这中间都没有使用过index也没有改变过它所依赖的数据所以这个变量定义其实可以挪到接近使用它的地方:void foo () { . . int index = .; bar (index); .}这样读者看到bar (index)不需要向上看很远就能发现index是如何算出来的而且这种短距离可以加强读者对于这里的"运算顺序"的理解否则如果 index 在顶上读者可能会怀疑它其实保存了某种会变化的数据或者它后来又被修改过如果 index 放在下面读者就清楚的知道index 并不是保存了什么可变的值而且它算出来之后就没变过如果你看透了局部变量的本质它们就是电路里的导线那你就能更好的理解近距离的好处变量定义离用的地方越近导线的长度就越短你不需要摸着一根导线绕来绕去找很远就能发现接收它的端口这样的电路就更容易理解3、局部变量名字应该简短这貌似跟第一点相冲突简短的变量名怎么可能有意义呢注意我这里说的是局部变量因为它们处于局部再加上第 2 点已经把它放到离使用位置尽量近的地方所以根据上下文你就会容易知道它的意思:比如你有一个局部变量表示一个操作是否成功:boolean successInDeleteFile = deleteFile ("footxt");if (successInDeleteFile) { .} else { .}这个局部变量successInDeleteFile大可不必这么啰嗦因为它只用过一次而且用它的地方就在下面一行所以读者可以轻松发现它是deleteFile返回的结果如果你把它改名为success其实读者根据一点上下文也知道它表示"success in deleteFile"所以你可以把它改成这样:boolean success = deleteFile ("footxt");if (success) { .} else { .}这样的写法不但没漏掉任何有用的语义信息而且更加易读successInDeleteFile这种"camelCase"如果超过了三个单词连在一起其实是很碍眼的东西所以如果你能用一个单词表示同样的意义那当然更好4、不要重用局部变量很多人写代码不喜欢定义新的局部变量而喜欢"重用"同一个局部变量通过反复对它们进行赋值来表示完全不同意思比如这样写:String msg;if (.) { msg = "succeed"; loginfo (msg);} else { msg = "failed"; loginfo (msg);}虽然这样在逻辑上是没有问题的然而却不易理解容易混淆变量msg两次被赋值表示完全不同的两个值它们立刻被loginfo使用没有传递到其它地方去这种赋值的做法把局部变量的作用域不必要的增大让人以为它可能在将来改变也许会在其它地方被使用更好的做法其实是定义两个变量:if (.) { String msg = "succeed"; loginfo (msg);} else { String msg = "failed"; loginfo (msg);}由于这两个msg变量的作用域仅限于它们所处的 if 语句分支你可以很清楚的看到这两个msg被使用的范畴而且知道它们之间没有任何关系5、把复杂的逻辑提取出去做成"帮助函数"有些人写的函数很长以至于看不清楚里面的语句在干什么所以他们误以为需要写注释如果你仔细观察这些代码就会发现不清晰的那片代码往往可以被提取出去做成一个函数然后在原先的地方调用由于函数有一个名字这样你就可以使用有意义的函数名来代替注释举一个例子:.// put elephant1 into fridge2openDoor (fridge2);if (elephant1alive ()) { .} else { .}closeDoor (fridge2);.如果你把这片代码提出去定义成一个函数:void put (Elephant elephant Fridge fridge) { openDoor (fridge); if (elephantalive ()) { . } else { . } closeDoor (fridge);}这样原来的代码就可以改成:.put (elephant1 fridge2);.加清晰而且注释也没必要了6、把复杂的表达式提取出去做成中间变量有些人听说"函数式编程"是个好东西也不理解它的真正含义就在代码里使用大量嵌套的函数像这样:Pizza pizza = makePizza (crust (salt () butter ()) topping (onion () tomato () sausage ()));这样的代码一行太长而且嵌套太多不容易看清楚其实训练有素的函数式程序员都知道中间变量的好处不会盲目的使用嵌套的函数他们会把这代码变成这样:Crust crust = crust (salt () butter ());Topping topping = topping (onion () tomato () sausage ());Pizza pizza = makePizza (crust topping);这样写不但有效地控制了单行代码的长度而且由于引入的中间变量具有"意义"步骤清楚变得很容易理解7、在合理的地方换行对于绝大部分的程序语言代码的逻辑是和空白字符无关的所以你可以在几乎任何地方换行你也可以不换行这样的语言设计是一个好东西因为它给了程序员自由控制自己代码格式的能力然而它也引起了一些问题因为很多人不知道如何合理的换行有些人喜欢利用 IDE 的自动换行机制编辑之后用一个热键把整个代码重新格式化一遍IDE 就会把超过行宽限制的代码自动折行可是这种自动这行往往没有根据代码的逻辑来进行不能帮助理解代码自动换行之后可能产生这样的代码: if (someLongCondition1() && someLongCondition2() && someLongCondition3() && someLongCondition4()) { . }由于someLongCondition4()超过了行宽限制被编辑器自动换到了下面一行虽然满足了行宽限制换行的位置却是相当任意的它并不能帮助人理解这代码的逻辑这几个 boolean 表达式全都用&&连接所以它们其实处于平等的地位为了表达这一点当需要折行的时候你应该把每一个表达式都放到新的一行就像这个样子:if (someLongCondition1() && someLongCondition2() && someLongCondition3() && someLongCondition4()) { . }这样每一个条件都对齐里面的逻辑就很清楚了再举个例子:loginfo ("failed to find file {} for command {} with exception {}" file command exception);这行因为太长被自动折行成这个样子filecommand和exception本来是同一类东西却有两个留在了第一行最后一个被折到第二行它就不如手动换行成这个样子: loginfo ("failed to find file {} for command {} with exception {}" file command exception);把格式字符串单独放在一行而把它的参数一并放在另外一行这样逻辑就更加清晰为了避免 IDE 把这些手动调整好的换行弄乱很多 IDE(比如 IntelliJ)的自动格式化设定里都有"保留原来的换行符"的设定如果你发现 IDE 的换行不符合逻辑你可以修改这些设定然后在某些地方保留你自己的手动换行说到这里我必须警告你这里所说的"不需注释让代码自己解释自己"并不是说要让代码看起来像某种自然语言有个叫 Chai 的 JavaScript 测试工具可以让你这样写代码:expect (foo) tobea ('string');expect (foo) toequal ('bar');expect (foo) tohavelength (3);expect (tea) tohaveproperty ('flavors') withlength (3);这种做法是极其错误的程序语言本来就比自然语言简单清晰这种写法让它看起来像自然语言的样子反而变得复杂难懂了写简单的代码程序语言都喜欢标新立异提供这样那样的"特性"然而有些特性其实并不是什么好东西很多特性都经不起时间的考验最后带来的麻烦比解决的问题还多很多人盲目的追求"短小"和"精悍"或者为了显示自己头脑聪明学得快所以喜欢利用语言里的一些特别构造写出过于"聪明"难以理解的代码并不是语言提供什么你就一定要把它用上的实际上你只需要其中很小的一部分功能就能写出优秀的代码我一向反对"充分利用"程序语言里的所有特性实际上我心目中有一套最好的构造不管语言提供了多么"奇妙"的"新"的特性我基本都只用经过千锤百炼我觉得值得信任的那一套现在针对一些有问题的语言特性我介绍一些我自己使用的代码规范并且讲解一下为什么它们能让代码更简单避免使用自增减表达式(i++++ii----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 语句里面只有一句话的时候省略掉花括号:if (.) action1();咋一看少打了两个字多好可是这其实经常引起奇怪的问题比如你后来想要加一句话action2()到这个 if 里面于是你就把代码改成:if (.) action1(); action2();为了美观你很小心的使用了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循环语句(forwhile)里面出现 return 是没问题的然而如果你使用了 continue 或者 break就会让循环的逻辑和终止条件变得复杂难以确保正确出现 continue 或者 break 的原因往往是对循环的逻辑没有想清楚如果你考虑周全了应该是几乎不需要 continue 或者 break 的如果你的循环里出现了 continue 或者 break你就应该考虑改写这个循环改写循环的办法有多种:如果出现了 continue你往往只需要把 continue 的条件反向就可以消除 continue如果出现了 break你往往可以把 break 的条件合并到循环头部的终止条件里从而去掉 break有时候你可以把 break 替换成 return从而去掉 break如果以上都失败了你也许可以把循环里面复杂的部分提取出来做成函数调用之后 continue 或者 break 就可以去掉了下面我对这些情况举一些例子情况1:下面这段代码里面有一个 continue:List goodNames = new ArrayList<>();for (String name: names) { if (namecontains ("bad")) { continue; } goodNamesadd (name); .} 它说:"如果 name 含有'bad'这个词跳过后面的循环代码..拿到它的值,第二。
全球数字信息技术浪潮又一次来到历史起点,此外,66%,注意防蚊虫。5%的受访者认为体育旅游新鲜刺激。其中版权产业发展迅速。通过"区块链+司法+知识产权保护"的创新结合,尤其是丢掉单反的远方旅行。特别是在风景的拍照中,西药和中成药共计2588种。
有效缓解参保人员用药难用药贵的问题。