阿里妹攻略:赶不上项目进度,欠了很多技术债怎么办?业务逻辑复杂,如何处理比较好?你想复制和修改类似的功能吗?重用?代码注释怎么写?好的代码对个人和团队来说都很重要,但是写出好的代码是一件非常困难的事情,需要长期的经验积累和学习。关于写出好的代码,本文作者分享了6个比较重要的入门要点,希望对同学们有所启发。写了很多年代码,总觉得如何写出干净优雅的代码并不是一件容易的事情。根据10000小时刻意训练定理,假设每天8小时,每月20天,每年12个月,大约需要5年时间才能成为大师。事实上,我们在日常工作中不可能真的花8个小时写代码,大部分时间都是在完成任务。当业务压力大的时候,我们可能想要达到的目标就是如何让功能尽快发挥作用,代码是否干净优雅很可能不是放在第一位的,而是来得有多快。在这样的情况下,很容易欠下技术债。时间长了,这样的代码基本无法维护,只能推倒重来。这个成本非常高。欠债要还只是时间问题,到了还款的时候,他们还要支付额外昂贵的利息。还债的人可能是他自己,也可能是他的继任者,但在还债的是这个团队。所以从团队的角度来说,写出好的代码是一件非常有必要的事情。如何写出干净优雅的代码是一个非常难的课题。我还没有找到通用的解决方案。这更像是一种权衡。你可以稍微讨论一下。代码是写给人看的,还是写给机器看的?在大多数情况下,我会认为代码是写给人们阅读的。虽然代码的最终执行者是机器,但实际上代码更多时候是供人阅读的。我们来看看一段代码的生命周期:开发-->单元测试-->CodeReview-->功能测试-->性能测试-->上线-->运维、Bug修复-->>在线测试-->离线退出。从开发到上线的时间可能是几周或者几个月,但是上线运维和bug修复的周期可能是几年。在过去的几年里,原作者几乎不可能维护它。后继者如何理解前面的代码逻辑是极其关键的。如果不能维持,就只能重做。所以我们在项目中经常看到的是,看到前人的代码,我们都觉得是垃圾,写的乱七八糟。我应该自己重写它。甚至在开发的过程中,别人都需要来CodeReview,如果自己看不懂代码,还怎么去做review。而且您不想因为其他人无法理解您的代码而不得不打电话给您寻求帮助而休假。我对此印象深刻。记得我打工的时候,放假回老家了。突然,有同事打来电话,问我怎么解决。当时手机还收漫游费,非常贵,但我仍然必须支持直到我的电话费用完。所以代码主要是写给人看的,是我们交流的方式。虽然那些非常好的开源项目都有文档,但是我们还是多看他们的源码。如果一个开源项目中的代码难以阅读,那么这个项目基本上不会流行。因为代码是我们开发者沟通的基本方式,甚至可能口头讨论不清楚的地方,我们可以通过代码说清楚。我认为代码的可读性是第一位的。估计每个公司都有自己的代码规范,遵循相关规范保持统一的代码风格是第一步(推荐谷歌代码规范[1]和微软代码规范[2])。规范一般包括如何命名变量、类和函数。函数应该尽可能短和原子化,不要做多件事。类的基本设计原则等。另外一个建议是多了解开源项目中的代码。KISS(Keepitsimpleandstupid)一般大脑的工作记忆容量为5-9。如果事情太多或太复杂,大多数人不能直接理解和处理。通常我们需要一些辅助手段来处理复杂的问题,比如记笔记、画图,这有点类似于内存不够时借用外存。学CS的同学都知道,外存的访问速度肯定没有内存访问快。另外,一般情况下,复杂逻辑出错的可能性要比简单情况大很多。在复杂的情况下,可能会有很多代码分支。我们是否可以考虑到每一种情况是困难的。.为了使代码更加可靠和易于理解,最好的方法是保持代码简单,在处理问题时尽量使用简单的逻辑,不要有太多变量。但真正的问题并不总是那么简单,那么复杂的问题如何处理呢?与其借用外部存储,我更喜欢将复杂的问题分层抽象。网络通信是一件非常复杂的事情。中间用到的设备种类不计其数(手机、各种IOT设备、台式机、笔记本、路由器、交换机……),OSI协议对每一层进行了抽象,将每一层需要处理的情况大大简化.通过对复杂问题的分解和抽象,将我们在各个层次上要解决的问题进行简化。其实也类似于算法中的分治法。复杂的问题必须先拆解成小问题,这样才能简化解决方案。KISS还有另外一个意思,“如果没有必要,就不要增加实体”(奥卡姆剃刀原则)。CS里面有一句话“计算机科学中的所有问题都可以通过另一层间接来解决”。为了扩展系统和支持未来可能发生的一些变化,我们往往会引入一层间接层,或者增加一个中间接口。在做这些决定的时候,我们要多考虑是否真的有必要。添加一个额外的层给我们带来了易于扩展的好处,但它也增加了复杂性并使系统更加难以理解。对于代码,很可能我这里调用了一个API,并不知道真正的触发点在哪里,这样会增加理解和调试的难度。KISS本身就是一种权衡。需要通过抽象和拆分来简化复杂的问题。但是,是否需要更多的间接抽象来保存更改需要仔细考虑。DRY(Don'trepeatyourself)为了快速实现一个功能,如果知道之前有类似的,复制代码修改一下,这可能是最快的方法。但是复制代码往往是许多问题和错误的根源。一类问题是复制的代码中包含一些其他的逻辑,这部分可能不需要,因此可能存在冗余甚至一些额外的风险。还有一类问题就是在维修的时候,我们其实并不知道一个地方修好了之后还需要修多少其他的地方。这个问题出现在我过去的项目中。有一个问题,之前明明已经解决了,但是几天后,另一个客户在另一个路径上提出了类似的问题。同样的逻辑尽量只出现在一个地方,这样出现问题的时候可以一次性修复。这也是一种抽象。同样的逻辑,将其抽象成一个类或者一个函数,也有利于代码的可读性。是否写注释我个人的观点是大部分代码尽量不要注释。代码本身就是一种交流语言,编程语言一般比我们日常使用的口头语言更精确。在保持代码逻辑简单的同时,使用良好的命名约定,代码本身很清晰,读起来可能像一篇好文章。特别是在OO语言中,对象(名词)加上操作(一般是动词)就已经可以说明它在干什么了。再次将此操作的名词放在注释中并不会增加代码的可读性。并且在后续的维护中,会出现修改了代码,但是没有修改注释的情况。我在我做过的许多代码审查中都看到过这种情况。尝试编写尽可能易于理解的代码,而不是通过注释。当然,我并不反对所有的评论。公共API需要评论。应列出API的前置条件和后置条件,以说明如何使用此API。这也可用于自动产品API的文档。在一些专门的优化逻辑和负责任的算法中加入对这些逻辑和算法的解释还是很有必要的。做对一次,不要相信Refactoring会在未来发生。一般来说,把TODO写在代码里,等后面重构或者改进,基本上就没有前途了。我们可以在我们的代码库中搜索TODO,看看有多少,有多少是很多年前的。相信这个结果会让你大吃一惊(欢迎大家留言分享你的搜索结果)。尝试做对一次,不要相信你以后会回来重构代码。人们很懒惰。当前的事情一旦完成,moveon后回来处理的概率很小,除非下次真的需要修改代码。如果说不会回来,那这个TODO就没有意义了。如果你真的需要,不要单独留下这个问题。我见过有人留下一个TODO,抛出一个未实现的异常,然后几天后其他同学把这段代码拿到网上直接挂了。尽量不要TODO,一次性做好。你想写单元测试吗?我个人的观点是必须的,除非你只是做原型或者快速迭代废弃的代码。单元测试通常是由软件开发人员编写和运行的自动化测试,以确保应用程序的一部分(称为“单元”)符合其设计并按预期运行。在过程编程中,一个单元可以是一个完整的模块,但更常见的是一个单独的函数或过程。在面向对象的编程中,一个单元通常是一个完整的接口,例如一个类,但也可以是一个单独的方法。来自维基百科的单元测试就是确保我们写的代码确实是我们想要表达的逻辑。当我们的代码集成到一个大项目中时,后续的集成测试、功能测试甚至端到端测试不可能覆盖到每一行代码。如果单元测试做得不够,实际上会在代码中留下一些你不知道的黑洞。有一天调用者改变了一些东西,可能到了一个不常用的分支就挂了。我之前带的项目也出现过类似的情况。代码已经上线好几年了。一旦我稍微改变了调用者的参数。本来以为是个小改动,结果一上线就挂了。测试分支。单元测试就是保证我们写的代码按照我们想要的逻辑实现。我们需要尽可能做到比较高的覆盖率,保证自己的代码不留黑洞。关于测试,我想单独开一个讨论,所以先在这里简单说一下。写出好的代码确实非常难,需要考虑正确性、可读性、健壮性、可测试性、可扩展性、可移植性和性能。以上所讨论的只是我个人认为比较重要的一些入门点。要想写出好的代码,需要经过深思熟虑和实践才能真正达到目的!相关链接[1]https://google.github.io/styleguide/[2]https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
