0%

原文链接

理论与实践的不同在于实践要比理论站的更高——这一观点也适用于注释。理论上,给代码注释是很有价值的观点:便于读者掌握细节,解释将会发生什么。什么比帮助更有帮助?然而在实践中,注释通常会成为一种障碍。和其他写作形式一样,写好注释是一种能力,知道什么时候不写注释是更重要的能力。

当代码格式错误时,编译器、解释器和其他工具肯定会提出反对。假如一段代码是功能性的错误,审查、静态分析、测试、日复一日的生产环境中大量的bug将浮现出来。但注释做呢?在《The Elements of Programming Style Kernighan and Plauger》指出如果出错了,注释的价值为零(或负的)。然而一些注释经常“乱丢”在代码库中,并以永远不可能出错的方式存在着。它们成为错误和分心的持续来源,对程序员的思维造成一种微妙的拖累。

哪些注释没有技术上的错误,但对代码没有任何价值?一些注释就是噪音。鹦鹉学舌般重复代码流程对读者而言没有额外的意义——用代码和自然语言各表达一次的情况并不会让其更正确或更实际。注释掉的代码是不可执行的,所以它对读者和运行时而言没什么帮助。它很快会变得陈旧。版本相关的注释及注释掉的代码都试图解决版本控制和历史问题。但这些问题已通过版本控制工具解决(效果更好)。

一些代码库中的噪声和错误注释会导致程序员忽略掉所有的注释,或通过跳过、或通过主动隐藏的方式。程序员有足够的智慧绕过任何被破坏掉的东西:折叠注释;选择一个和背景色差不多的配色方案给注释;写一个注释过滤脚本。为了避免代码库中由于程序员的创造导致的失误,以及减少真正有价值的注释被忽略掉的风险,应该像对待代码一样去对待注释。每一次注释都应该给读者带来附加值,其他存粹占空间的部分就应该被删掉或者重写。

什么叫作有价值?注释应该说的事情代码不要也不能说。一段代码的注释应该说明数据结构和编程约定,其他的地方代码会‘自圆其说’。而不是糟糕的方法或类名,重命名他们。不用写一大段长函数的注释,儿时提取其中匿名函数(较小部分),并写清楚其意图。尽可能尝试通过代码来表达。你想表达的和你代码本身表达的差异都会成为有用的候选注释。注释意味着代码自己不能说的,而不仅仅是代码没有说的。

请认清现实,成功除了个人的不懈努力外,必须要天时地利人和。

《异类:不一样的成功启示录》,看书名会误以为这本书要揭示某项成功秘籍,可能是我太笨,没有get到任何一个只要这么做你就能成功的观点,相反,第一感觉,这是一本反成功学的书。因为书的本意只是换个角度审视“成功”背后的规律,但这些规律大部分是天注定,根本不可能通过个人努力来改变。

阅读全文 »

原文链接

在我大学的第一堂编程课上,老师发了两张BASIC代码卡片。在黑板上写下:“写一段程序用于输入并计算10个保龄球的平均成绩。”然后老师离开了教室。这道题有多难呢?我不记得最后是如何解决的,但我很清楚用到了FOR/NEXT循环,并且总过不超过15行代码。代码卡片,对于少年时期的你而言,是的,我们那时在把它敲进计算机前确实是手写代码,大概允许每人写70行。我很懵逼,为什么老师要给我们两张卡片。由于我的书法很搓,我就非常工整地把代码抄到另一张,希望在风格上得到加分项。

当时我就震惊了!下堂课我拿到成绩一看,差点不及格。(这也成了我大学剩下时光里的征兆。)在我工整的代码头上写道——“注释被你吃了?”

我和老师都知道一段程序是如何运行的还不够。这次作业的另一点教育了我,我的代码应该要解释给下一个到来的程序员。这真是难忘的一颗啊!

注释不是坏事。它们在程序的分支或循环控制语句中尤为重要。很多现代语言都有类似javadoc的工具,可以根据正确格式的注释语法自动构建API文档。这是非常好的开始,但还不够。你在代码内部还要说明代码会做些什么。编程格言:如何不好写,肯定不好读。这会伤害你的客户、你的员工、你的同事、以及未来的你。

另一方面,你可以在注释的路上走的很远。注释要做到很清楚而不是懵懂。利用注释来说明你的代码将完成什么。头部注释应该给予程序员足够的信息,让他不用读代码内容就可以调用它,行内注释应该帮助其他开发者修复或扩展你的代码。

在一次工作中,我不同意一个由我上级作出的设计决定。正如年轻人经常这么干,感觉很鸡贼,我把电子邮件里指导我使用他们设计好的模版,直接粘贴到文件的头部注释当中。而实际情况是,当代码提交后,经理会躲在某个咖啡店里把代码审查了。这是我第一次提到职业瓶颈。(It was my first introduction to the term career-limiting move最后这句不会翻译🙂)

原文链接

试图通过手动推理软件正确性的结果就是证明过程比代码本身还长,而且比代码共容易包含错误。自动化工具是首选,但并非每次都有效。有一条相对折中的方案:半正式化推理正确性。

基本方案便是考虑把所有代码都分割成小段,从一行、一个功能调用、到每个块不超过10行,争论它们的正确性。这些论点要足够强大,才能说服你那魔鬼般的程序员同行。

应该选择一个部分,便于在每个端点处的程序状态(即程序计数和处于生命周期内的对象值)都能满足易于描述的特性,并且该部分的功能(状态转换)很容易描述为一个单任务——这些可以让推理更简单。这些端点能够概括函数的前置和后置条件等概念,以及循环和类(的实例)的不变量。尽可能使各个部分相对独立,简化推理,当然,随时修改它们也是必不可少的。

很多历经编程实战的知识(可能不够好)以及’好的’思想可以简化推理。因此,仅仅是想要推理你的代码,就说明你已经在考虑一种更好的代码风格和数据结构了。不必惊讶,这些实战大多是经得起静态代码分析器的检验的:

  1. 避免使用goto语句,否则会与远处的部分高度耦合。
  2. 避免使用可变的全局变量,这会让所有地方都依赖他们。
  3. 每一个变量都应该尽可能让其作用域最小。例如,一个局部对象应该在它第一次使用的时候才声明。
  4. 在关联时,让对象不可变。
  5. 通过空白符让代码可读,不论水平还是垂直(排版)。例如,对其有关联的数据机构,以及用空行把两个小节分开。
  6. 让代码自成文档,选择描述(但相对简短的)对象名、类型、函数等。
  7. 如果你需要一个嵌套的部分,最好把它变成函数。
  8. 让你的函数短小且聚焦于单一任务上。老旧的24行限制原则依然受用。尽管屏幕尺寸和分辨率一只在升级,但人类的认知能力从1960年后就没怎么变过。
  9. 函数参数应尽量少(4个不能再多了)。这并不是限制了函数的数据传递能力:把有联系的参数分组到同一对象中,能得益于对象的不变性以及保存推理,确保他们的统一性和连贯性。
  10. 更普遍的,每个代码单元,从代码块到库,都应该有一个窄接口。更少的通信可以降低必要的推理。这就意味着,返回内部状态的getters方法是一种责任——不要问一个对象拿到信息后如何工作。反之,应该问一个对象拿到信息后有没有开始工作。换句话说,封装的是全部,暴露的接口是窄的。

除了要推理它的合理性,讨论你的代码可以让你进一步理解它。传达你的见解,让每个人受益。

1
2
3
4
<滴滴滴滴滴滴>
你好啊~
你是GG还是MM
886

回顾起当年我们的网络用语,浑身鸡皮疙瘩呀😂。如果没记错,我第一次接触qq应该是在村里网吧,那个时候我刚上高中。对qq的概念就是网上聊天,疯狂加好友(女的),聊了没两句就开视频…靠!好丑!拉黑!。后来,我不在热衷于加陌生好友;再后来,我不在热衷于踩空间、炫钻、拼等级;现在,QQ对我而言仅仅是一款通信工具。时光飞逝,QQ不老,只是那一个个“上善若水”、“往事如烟”、“水晶之恋”都长大了。

阅读全文 »

原文链接

你应该做过代码审查。为什么?因为它能提高代码质量并降低错误率。但这不一定是你所认为的原因。

因为他们此前可能有一些审查的坏习惯,很多程序员都讨厌代码审查。我曾经在的单位都是等到产品上线部署之前才开始正式的代码审查。通常都是由架构师或者开发负责人做审查工作,一种站在整体框架审视所有的行为。这是在他们的软件开发流程手册中规定的,所以任何程序员都必须遵守。这可能是一些机构严格且正式的流程制度,但大多得不到执行。在很多机构中这项方案往往适得其反。审查员们会感觉像是被假释委员会裁决一样。审查员要同时抽空去阅读代码并抽空掌握系统中的所有细节。这些审查员很快就会变成该流程上的瓶颈,进而整个流程开始僵化。

代码审查的目的不是简单地纠错,而是应该分享知识并建立通用的编码指南。和其他程序员分享你的代码可以实现代码集体所有权。让团队里任何一个成员能够和其他成员一同浏览代码。通过尝试学习和理解来审查代码,而不仅仅是寻找错误。

代码审查时要尽量平缓。确保其评价具有建设性,不要尖酸刻薄。审查会上要以审查角色来向他人介绍,避免资历较老的人给团队其他人带来代码审查压力。这些角色可能会包含一个文档审查、异常审查、功能性审查等。这个方案可以帮助团队成员分担审查负担。

每周定期进行代码审查。花几个小时开发审查会议。每次会议都以轮流的方式担任审查员。记住要每次在团队中挑选的都是审查角色。让新人也参与代码审查。他们可能缺乏经验,但他们生涩的大学知识能够带来不同的视角。让专家提供他们的经验和知识。他们可以更快更准确地鉴别容易出问题的地方。如果团队有利用检查工具的编码约定,那代码审查将变得更容易流动。这样,关于代码格式的问题永远都不用带代码审查会当中讨论了。

让代码审查变得有趣或许是成功的最重要因素。审查是关于人的审查。如果审查会议是痛苦且呆板的,将很难调动大家的积极性。让其成为非正式代码审查,让团队成员都能从中分享知识。把任何讥讽的评价都抛开,然后带上蛋糕和棕色带午餐(一种自备的非正式午餐,边吃边交流)。

原文链接

就在几年前,我在一套Cobol系统上工作,这套系统不允许任何人改变代码行缩进,除非有充足的原因,因为将一行(代码)起始移到某个特殊列时会破坏某些东西。有的时候这种排版会引起误导,因此我们不得不非常仔细地阅读代码,因为我们无法信任它。该项政策必须消耗掉程序员很大精力。

有研究表明,我们编程时会花更多的时间来浏览和阅读代码,找出要改变的地方,而不是一直敲代码,这便是我们想优化的地方。

  • 更容易扫描。人类确实善于视觉模式匹配(我们可以在大草原上从残缺的一点信息中认出狮子),所以我可以通过创建与作用于无关的任何内容来帮助自己,以及大多数商业术语带来的“偶然复杂度”,通过标准化来淡化背景(好比大草原)。如果有行为逻辑相像的代码看起来也像,我的感知系统会帮我识别出不同的地方。这就是为何我会观察一个类在编译单元是如何布局的约定:常量、字段、公有方法、私有方法。
  • 富有表现力的布局。我们都学会花时间去寻找正确的命名,以便我们的代码能够更清楚地表达它的意图,而不仅仅是列出步骤,对么?代码的排版也是表达的一部分。首个切入点应该是团队基本代码通过自动格式化处理,然后我自己的代码进行手动调整。除非存在分歧,否则一个团队应该尽快融入到这种“手工制作”的风格中。一个格式化工具无法理解我的意图(我应该知道,我写过一次),对我而言它在换行和分组所反映的代码意图更重要,而不仅仅是代码语法。(Kevin McGuire把我从自动化格式代码的束缚中拯救了出来)
  • 紧凑的格式。我在屏幕上看到得越多,我通过滚动屏幕和切换文件看到完整的上下文就越多,换而言之我可以在脑海里保持较少的状态。长的程序注释和大量的空格是为了8个字符的名称与行打印更合理,但我现在通过IDE进行语法高亮和交叉链接(跳转)。像素成了限制我的因素,我希望每个人都能为我理解代码做出贡献。我希望排版可以帮我更好地理解代码,仅此而已。

一个非程序员朋友有次说这段代码看起来像诗一样。我也对这段确实不错的代码很有感觉,文本中的每个地方都有明确的目的,帮我了解其想法。不幸的是,写代码不可能像写诗一样罗曼蒂克。

一说李笑来,喷子们还有30秒到达战场,被他们碾碎🤬!没办法,这位亿万平民背负了太多争议,当然,大家关注他的焦点应该是背后的比特币。我对币圈没兴趣,而至于他在圈内的所作所为及其言论,也没兴趣,我只是从他被骂的结果本身,单纯而肤浅的得出结论——这人不太会讲故事。

阅读全文 »

原文链接

想象一下明天醒来看到的景象,建筑行业取得了百年突破。数百万的便宜、高效的机器人可以凭空制造物料,几乎零成本,可自我修复。而且更好的是:给一个靠谱的工程蓝图,机器人可以不需要人类的干预就完成建造,成本可忽略不计。

有个可遇见的发生在建造业的冲突,行业上游会发生什么?如果建造成本忽略不计了,那架构师和设计师何去何从?今天在投资建造之前需要先进行物理和计算机建模和严格测试。如果建造本身是免费的我们会烦恼吗?如果设计出错了,没什么大不了——找出问题所在,让我们的魔法机器人在建一个。这是未来可预见的。建模将会被淘汰,未完成的设计将在不停的建造和完善中改进,直至非常接近目标。对于一个不负责的监理而言,区分完成和未完成将是一件烦恼的事。

我们预估时间线的能力会逐渐消失。由于建筑成本会比设计成本更容易计算——我们更准确地知道安装大梁的成本和数量。随着可预估的任务缩小为零,可估计的设计时间就越来越少,开始站主导地位。结果就是生产更快,但“靠谱”的时间消失了。

当然,市场竞争机制仍然试用。随着建筑成本的消除,一家快速完成设计的公司能够在市场中占据优势地位。快速完成设计称为工程公司的核心推动力。当然,一些没有深思熟虑的设计将被看作未验证版本,只是看中早期发布的市场优势,然后说“这看起来够用了。”

一些生死攸关的工程将变得更仓促,但消费者应该受够了设计半成品所带来的苦。公司总是排出他们的魔法机器人去“修补”这些破损的建筑和机械。所有这些都指向一个反直觉的结论:我们大幅降低了建设成本,结果质量也降低了。

我们不应去惊叹上面这个故事在软件业同样上演着。如果我们同意代码即设计——这是个需要创造力的过程而非机械式的——这也可以解释软件危机。我们现在知道一个设计危机:对质量、经验证的设计需求已经超过了我们建造它们的能力。导致使用未完成设计的概率更高。

值得庆幸的是,该模型还是给我们提供了较好的线索。物理仿真等同于自动化测试;软件设计只有经过暴力测试后才算完成验证。为了使这些测试更有效,我们正在寻找能够检测大型系统中巨量状态的方法。改善编程语言和设计实践让我们重新燃起希望。最后一个不可避免的事实:伟大的设计是由伟大的设计师致力于掌握他们的技艺而产生的。代码也不例外。

原文链接

两个代码库。其中一个是这样的:

1
2
if (portfolioIdsByTraderId.get(trader.getId())
.containsKey(portfolio.getId())) {...}

你抓破头皮,想要搞懂这段代码要做什么。看起来像是从trader对象中取出一个ID,通过一张表查找另一张表,好吧,上面代码就是表中表的玩法,但好像又是在比较portfolio对象是否存在这张表中。哎呀,抓耳挠腮呀。继续从portfolioIdsByTraderId中寻找线索:

1
Map<int, Map<int, int>> portfolioIdsByTraderId;

渐渐的你发现,这应该和trader对象是否要访问某个特定portfolio对象有关。毫无疑问,你会发现更多相同的代码片段,或者更多看着像但很隐蔽的不同的代码片段——不论什么时候都要注意trader是否在访问某个特殊portfolio。

在另一个代码库:

1
if (trader.canView(portfolio)) {...}

这次不用挠头了。有不需要知道trader内部在干嘛。或许里边就有藏着各种表。但那是trader的业务,和你没关系。

现在,你更希望用哪个代码库完成工作?

从前我们只有很基础的数据结构:bit和char(当然还有bytes,我们假装用于字母和标点符号)。小数点比较棘手,因为我们的10个数字在二进制中不好弄,所以我们又为浮点类型扩充了一些内存长度。这次有了array数组和string(其实就是不同的数组)。然后我们又增加了堆栈、队列、哈希、链表、跳表以及大量的根本不存在真实世界的数据结构。“计算机科学”就是花大量的时间把真实世界映射到我们限定的数据结构中。真正的大师甚至能够记住他们是如何做到的。

我们有了user类型定义!OK,这不是什么新闻,但它的确改变了游戏规则。如果你的业务领域包含像traders和portfolios的概念,你可以对它们建模,称之为,Trader和Portfolio。但比这更重要的,你也要会给它们两者之间的关系进行建模。

如果你不用领域术语编写代码,而是创建一个心照不宣(佛曰:不可说)的未知事物,就会出现同样是int,在这里表示trader,跑到那里有代表portfolio。最好是不要将它们混淆!如果你直接通过一套套算法去表达某种键表的存在关系(比如,“一些trader禁止去查看一些portfolios”——属于非法行为),你并没有给审计人员带来任何好处。

下次到来的程序员又不懂里边有什么猫腻,所以为何不直接把它表述清楚呢?用一个键去检查另一个键是否存在,太模糊了吧!怎能让其他人直观地理解,实现“利益冲突”的业务规则到底他妈的在哪?

将一个领域概念清晰地在代码中表达出来,意味着其他人可以更容易地理解领域并在对应算法的地方加以改进。这也意味着如果一个领域模型需要迭代了——你对领域理解更深的时候,你可以更好地定位到对应的代码。加上良好的封装,业务规则很可能只会存在一个地方,你可以不依赖任务其他代码,直接修改它。

下个到来的程序员会因为只花少量的时间就进入工作而感激你。下个到来的程序员也许就想成为你这样的人。