程序员修炼之道 11:当你编码时

news2024/12/23 15:01:23

不记录,等于没读。

这里是我阅读《程序员修炼之道》这本书的记录和思考。


编码阶段不是机械性工作,而是每一分钟都要做出决定——深思熟虑后的决定。如果把编码阶段当成机械性工作,认为这个阶段只是把设计翻译成可运行的代码段,这种态度是项目失败的最重要原因。

本章谈论编码阶段中应该注意的事项。

听从蜥蜴脑

蜥蜴脑 (Lizard Brain) 在英文环境中指大脑最原始的部分,这部分主要负责人类的生存功能,通常引申为人类的本能反应、直觉。

“听从蜥蜴脑”的意思是:利用好你的直觉

看到这里我的直觉反应是:“这不科学”。直觉这东西听起来相当不靠谱,没法度量,意味着无法可靠复现。总之,不具有确定性。
对我来说这是一个全新的观点,我对此持怀疑态度。因此我进行了一番查证,认为它有一定的道理。

直觉就是对我们无意识大脑中存储的模式的一种反应。有些是天生的(应对天敌时的闪避),有些是不断重复学习到的(医生诊断疾病、下棋策略)。

《程序员思维修炼》给出了一个观点:新手依赖规范和指南,而专家凭直觉工作

新手刚接触一个行业,他们不知道从何开始,因此需要明确、逐步的规范来执行任务,给他一个任务清单再好不过了。而专家在一个行业中摸滚打爬多年,他们知道哪些是无关紧要的细节,哪些是非常重要的细节。专家非常擅长做有针对性的特征匹配

比如有一个程序员。积累了经验后,大脑就会逐渐形成一层又一层的隐形知识:哪些方法有效,哪些方法无效,某种错误的可能原因,以及你在日常工作中注意到的所有事情。

直觉无法用语言表达,但会让你感受到:你会感到紧张,或者不舒服,或者觉得这件事的工作量实在太大了。

当直觉提醒你时,你要能注意它正在发生

当开启新项目,或仅仅启动一个新模块都可能让人不安,以至于我们会推迟迈出第一步。造成不安的原因可能有两种:

  1. 直觉在告诉你一些事情。这种原因很重要,当面对一项任务时感到不情愿或是一种挥之不去的疑虑,这是你过去的经验在向你示警:哪里有些不对劲。你提前注意到这些征兆,当不对劲的事情显现出来后,你更容易规避问题。
  2. 担心自己做不好,认为项目超出了自己的能力范围。这是一种合理的恐惧,应对也比较简单:去做。

需要警惕下面的情况:
有一段时间了,你觉得编程像在泥泞中爬坡。每走一步都需要极大的努力,走上三步就会后滑两步。你内心对此烦躁不安,但仍要求自己坚持下去,因为你觉得这就是自己的工作,而自己是个有始有终的专业人士。

真相是,此时你要做的不是坚持,而是停下来想一想,内心的抗拒很可能是告诉你:正常的开发不应如此痛苦——也许真正的原因是设计有问题、编码有问题,或者需求的理解有问题。

如果你不倾听直觉,找到根本原因,你就是在制造 BUG。倾听直觉的方法是:

  1. 首先,停止正在做的事情。给自己一点时间和空间,让大脑自行整理思绪。
  2. 如果第 1 条不起作用,就试着把问题外化。外化就是指将内在的问题转化为外部的形式,比如尝试把问题写在纸上、向同事解释一下是怎么回事。这样可以把问题暴露给大脑的不同部分,从而更好的处理难题。
  3. 如果还是不起作用,可以用做原型的方式来干这事。原型是一种有效的脑力突破方法。告诉自己正在做原型,因此可以不考虑过多细节,而是专心于测试一两个疑惑点。

如果在别人编写的代码上工作,记住这些人的直觉和你不同,他们会做不同的决定,但不一定不好,仅仅是不同而已。

不仅仅是编码要听从直觉,设计也是。如果一个设计让你感觉不妥,或是一些需求让你觉得不爽,就停下来分析这些感觉。

巧合式编程

有些程序能够正常运行,但是基于巧合,靠得是运气。

在这里插入图片描述

怎样算是巧合式编程?

你不知道原理,但是代码能正常工作。这看上去还不错,至少能正常工作,不是吗。问题是,当你再往上添加功能时,它很可能突然不能工作了。你要花费大量时间来修复问题,有时还会反复修复。

代码会出错,是因为一开始就不知道它为什么能工作!某人做了一些尝试,发现程序似乎跑起来了。他不知道其中的原理,就会畏惧修改这些代码:现在能工作了,最好别碰这块代码……即便这些代码不合理,依赖了未被承诺的特性。

不要依赖巧合式编程:

  1. 不要依赖文档中没有记载的行为,没有记载的东西随时都可能改变。
  2. 如果做不到只依赖文档中的行为,不管出于什么原因,把你的假设记录下来。
  3. 找到正确的做法,而不是凑合。比如结果总是与预期有偏移,不去找根源而是添加一个经验值来满足预期。
  4. 不要假设,要证明。
  5. 找到恰好能用的答案和找到正确的答案不是一回事。比如网上搜索到的代码要能理解其原理,或许他的代码环境与你的并不相同。

如何深思熟虑地编程?

  • 尽可能早地捕获并修复错误.
  • 时刻注意你在做什么,不要让事情失控。
  • 能够向初级程序员解释清楚程序的逻辑。
  • 理解你正在使用的技术,确定它为什么能用。
  • 只依赖可靠的东西。不要依赖假设。
  • 将假设文档化。
  • 测试假设。如果假设 long 占用 4 个字节,那么用断言来测试这个假设。
  • 随时准备重构,让代码一点点变好。

算法速度

无时无刻不在评估:每当编写包含循环或递归调用的东西时,都要下意识地检查运行时间和内存需求。

一些常识:

  1. 简单循环:O(n),时间增加和 n 线性相关。
  2. 嵌套循环:O(n2)
  3. 对有序数列二分查找、遍历二叉树:O(lg n)
  4. 快速排序:O(n lg n)

需要考虑代码本身,当 n 较小时,一个简单的 O(n2) 循环比一个复杂的 O(n lg n) 算法表现要好得多。在选择算法时,需要务实一些。最快的算法并不总是最适合当前工作的。

重构

随着需求的增加,有必要重新考虑早期的决策,对部分代码进行重构。

之前我一直将编程与建筑做类比。

  • 建筑强调结构设计的重要性,程序也需要良好的软件架构;
  • 建筑由不同的构件组成,比如墙、窗户等,程序也强调模块化;

但建筑隐喻也意味着程序一旦完成之后,主体结构就不能再动了,只能做有限的维护,这显然与程序的实际情况不符。作者在书中提出了另一种隐喻,将编程比作 园艺

  • 程序不是一次设计完成,而是随着时间逐步发展和完善的,正如花园里的植物随着时间生长和变化。
  • 根据植物的成长情况进行调整,如修剪枝叶、施肥等。程序员同样需要根据程序运行的情况进行维护和优化。

管理者可能对建筑的隐喻满意,因为它是可重复的,管理上有严格的汇报层次结构。但实际的程序更接近于园艺,也许某个程序太庞大,需要一分为二。需要删除或修建不合理的计划。

重构 的定义是:重构是一种技术,需要遵守纪律,必须遵循重构规范。这项技术用于重组现有代码体,在不改变其外部行为的前提下改变其内部结构。根据定义,可以提取出两个关键点:

  1. 这项活动是有纪律的,不能随意为之;
  2. 程序的外部行为不变;重构的过程中不能添加新功能。

重构是一项日复一日的工作,小步进行,这样能降低风险。尽早重构,经常重构。重构,和大多数事情一样,在问题很小的时候做起来更容易,要把它当做编码日常活动。重构前,必须有良好的自动化测试来保证重构没有改变外部行为。

何时该重构?

  1. 重复
  2. 非正交设计
  3. 知识过时
  4. 对需求认识更深刻时
  5. 通过了测试。测试驱动开发提倡一旦通过测试,立刻进行重构。

重构的核心是重新设计:根据新的事实、更深的理解、更改的需求等重新设计。重构是一项需要慢慢地、有意地、仔细地进行的活动。如果你执拗地非要将海量代码统统撕毁,可能会发现,自己所处的境地,比开始时更加糟糕。

怎样重构?

  1. 不要让重构和添加功能同时进行。
  2. 在开始重构之前,确保有良好的测试。这样,如果有变更破坏了任何东西,都将很快得知。这条非常重要,是重构的基础。
  3. 采取简短而慎重的步骤。保持小步骤,并在每个步骤之后进行测试。

下一次看到一段代码与它应该有的样子不符时,要把它修好。这其实是在控制疼痛——尽管现在很痛,但以后会痛得更厉害,那么就忍痛赶紧干完。

为编码测试

在本书第一版的时候(1999 年),大多数开发人员都不写测试。现在(本书第二版出版于 2019 年),如果有开发者仍然没有编写测试,至少他知道自己应该去做

测试的重要性是什么?请记住:测试的好处发生在你考虑测试及编写测试的时候。测试给了我们一个观察代码的机会,它迫使我们编写低耦合代码。这是测试带来的最大好处,其它收益都是附带的,包括找到 BUG

测试时代码的第一个用户。测试所提供的反馈至关重要,可以指导编码过程。与其它代码紧密耦合的函数很难进行测试,因为你必须在运行函数前设置好所有环境。所以:让你的代码可测试,就可以减少它的耦合。

所以现在,我的一个感悟是:我不再关注代码模块化,转而关注可测试性。如果一段代码是可测的,它就是模块化的。这就是我推崇测试驱动开发的缘由:它不但减少了错误,还倒逼你写出模块化的代码。

测试驱动开发的理念是先写测试再写业务代码,基本循环是:

  1. 决定添加一小部分功能。
  2. 编写一个测试,毫无疑问,这个测试会编译失败,因为它测试的对象还没有编写。
  3. 编写产品代码,可以恰好让刚刚失败的测试通过。
  4. 再次编写测试,恰好让测试再次失败。
  5. 编写产品代码,可以恰好让刚刚失败的测试通过。
  6. … 如此循环,每个循环周期应该非常短,可能几秒到几分钟。
  7. 一个小功能完成后,重构。

然而,不要成为测试驱动开发的奴隶,花费太多时间来确保总是有 100% 的测试覆盖率。不要忘记停下来看看大局,要让你的代码离解决方案更近,而不是为了获得大量的“测试通过”消息。

在计算机科学稚嫩的童年,有两种设计学派:自上而下和自下而上。这两个学派实际上都没成功,因为它们忽略了软件开发中最重要的一个方面:我们不知道开始时在做什么。我们坚信,构建软件的唯一方法是增量式的。构建端到端功能的小块,一边工作一边了解问题。应用学到的知识持续充实代码,让客户参与每一个步骤并让他们指导这个过程。

端到端指的是从用户的角度出发,构建一个完整的工作流程或功能链条,确保从输入到输出整个过程都是连贯的。
端到端的方法强调在整个开发过程中保持对最终用户价值的关注,确保每次迭代都能够提供可见的价值,并且在整个系统的生命周期内保持一致性和可用性。

软件被部署到生产环境前,可以提供模块内部状态的各种视图,跟踪消息日志文件就是这样一种机制。日志消息应该采用规范一致的格式,便于理解,也便于软件自动解析。
另一种机制是按下特定组合键或是特定 URL 时,弹出一个诊断窗口,里面有各种状态信息。

你编写的所有软件最终都将被测试,如果不是由你和你的团队做测试,那么就将由最终的用户去测试。在大多数情况下,测试先行,包括测试驱动开发,可能是最佳选择。在编码期间进行测试是一个备选方案。最糟糕的做法是“以后再测”,这么说的人大概没听过勒布朗(LeBlanc)法则:稍后等于永不(Later equals never)。

对待测试代码要像对待任何产品代码一样。测试是编程的一部分,不该留给其他部门的人去做

测试、设计、编码——都是在编程

基于特性测试

一个人写代码,然后再写测试,有可能代码可以通过测试,只是因为它是根据你的理解去完成工作的。所以可以让计算机来做一些测试,它不会受你的先入之见影响。我们的代码要遵循一定的契约和不变式,可以用这些特性来自动化我们的测试,这就是基于特性测试。

  • 对列表排序,其数据元素个数应不变。
  • 订单处理和库存控制系统,物品不会凭空出现,也不会凭空消失。

基于特性的测试会让你从契约式和不变式中考虑代码,你会思考什么不能改变,什么必须为真。这种额外的洞察力会对代码产生神奇的影响,可以消除边界情况。

基于特性的测试是对单元测试的补充,二者处理不同的关注点。

出门在外注意安全

数据泄漏、系统被劫持、网络欺诈等越来越多。在绝大多数情况下,这并不是因为攻击者非常聪明,他们甚至都谈不上有多大能力。开发人员实在太粗心了

在编写代码时,你可能反复经历着“可以跑了!”和“为什么不工作?”的循环,间或抱怨一句“这不可能啊……”经历几次起伏后,很容易对自己说:“吁,都跑起来!”并宣告代码已经完成。当然,还没有完成。

接下来你需要分析代码中可能出错的路径,考虑传入错误的参数、泄漏的资源、可能不存在的资源等可靠性事项。

当然,这也还没完,因为这些都是内部错误。接下来,要考虑有哪些外部参数可能会搞砸系统。或许你会说:“这并不是什么重要东西,甚至没人知道这台服务器……”

醒醒吧,通过隐藏来实现安全性是行不通的

安全性的基本原则:

  1. 将攻击面积最小化:
    • 代码复杂性滋生攻击载体,编写简单、简洁的代码。
    • 输入数据是一种攻击载体,永远不要信任来自外部实体的数据。
    • 未经身份认证的服务成为攻击载体
    • 经过身份认证的服务成为攻击载体,确保授权用户的数量保持在最小范围。
    • 输出数据成为攻击载体,确保输出的数据适合该用户的权限,对危险信息进行截断或混淆(比如手机号、身份证)。
    • 调试信息成为攻击载体
  2. 最小特权原则:在最短的时间内使用最少的特权。
  3. 安全的默认值。
  4. 敏感数据要加密。
  5. 维护安全更新:尽早打上补丁。

事物命名

事物应该根据它们在代码中扮演的角色来命名。只要你要创建某个东西时,你要停下来思考“我创建这个的动机是什么?”






每一份打赏,都是对创作者劳动的肯定与回报。
千金难买知识,但可以买好多奶粉

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2145150.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

初识 performance_schema:轻松掌握MySQL性能监控

什么是 performance_schema performance_schema 是 MySQL 5.8 版本的一个强大功能,它就像是一个内置的**“性能侦探”**,专门用来监控和分析 MySQL 服务器的资源消耗和等待情况。有了它,数据库管理员和开发者就能实时了解服务器的运行状态&a…

【LLM】中国在 GPT/LLM 大模型上是否已经实现了弯道超车?

还是谈一下现状吧。中国的大模型公司与美国的大模型公司其实在数量上可能中国更多一些吧。 美国的 OpenAI:No.1,毫无疑问!Google:尽管落了,但是依然是全球第二的实力吧?Meta:开源全靠它家的Ll…

字幕编辑用什么软件好?盘点国内外7款视频加字幕软件,简单高效!

视频添加字幕被认为是让观众更好理解您在视频中讲述内容的最佳和最常见的方式。例如,您可以给视频中的某些文字添加不同的颜色,以帮助观众识别视频中的角色。然而,在制作视频过程中,添加字幕往往是个耗时耗力的任务。因此&#xf…

rsync 服务详解

目录 1.前言 2. rsync 常用选项 3.rsync应用场景 4.rsync使用模式 5.rsync推与拉​编辑 5.1rsync推送 ​编辑5.2rsync拉取 6.rsync本地模式 7.远程模式 8.rsync守护进程 8.2改配置文件 8.3添加虚拟用户 8.4创建密码文件 8.5给文件给予权…

牛客周赛 Round 59(思维、构造、数论)

文章目录 牛客周赛 Round 59(思维、构造、数论)A. TDB. 你好,这里是牛客竞赛C. 逆序数(思维)D. 构造mex(构造)E. 小红的X型矩阵F. 小红的数组回文值(数论、范德蒙恒等式) 牛客周赛 Round 59(思维…

C++之仿函数和虚函数

仿函数(Functor)和虚函数(Virtual Function)是 C 中两个不同的概念,它们在功能和使用场景上有显著的区别。 1. 仿函数(Functor) 定义: 仿函数(也称为函数对象&#xf…

【原理图PCB专题】案例:原理图设计检查为什么要检查全局网络?

本案例发生在新人的PCB设计文件中,当然就算硬件老人们,其实只要不注意也很容易出现这种全局网络乱用的问题。 如下所示是给新人的接口参考图纸,要求使用嘉立创绘制16个相同的接口做一个工装板。同时还要增加单片机实现切换控制功能。可以看到座子的24个管脚中使用到了3.3V、…

五种MP3转换器推荐,音频转换不发愁!

每当遇到心仪的歌曲或珍贵的音频资料,却因为格式不兼容而无法在所有设备上顺畅播放时,是否感到一丝遗憾?现在,是时候告别这种烦恼了!分享五种功能强大、操作简便的MP3转换器,从此音频转换不发愁&#xff01…

MySQL字符集的转换

背景介绍 在使用MySQL过程中,如果字符集配置不当,可能会出现插入失败、数据乱码、 索引失效、数据丢失、查询不到期望结果等一系列使用异常的情况。因此,熟练掌握MySQL字符集和比较规则的配置方法,并在此基础上了解MySQL字符集与…

AI编程的特点及SCSAI平台在AI编程方面的一些思路

团长团 AI智造AI编程 2024年09月18日 18:25 北京 说先来看看AI编程的优缺点,然后我们再看看SCSAI在AI编程方面的一些可能选择 使用AI编程的优点 ‌AI编程的优点包括提升编程效率、降低编程门槛、优化程序结构、加强软件可靠性、促进跨领域融合,而缺点则…

龙海家园的免费停车点探寻

​第一次去龙海家园就把我羡慕到了,楼下就是鲤鱼门地铁,龙海家园底商的餐饮好吃又实惠,还有特别多的超市,空中花园也很大,还可以共享前海基金小镇的花园环境。虽然我看到很多车排队等进龙海家园,但是我还是…

红帽 Quay- 配置镜像代理缓存

《OpenShift / RHEL / DevSecOps 汇总目录》 说明:本文已经在 Quay 3.12 环境中验证 说明:可先根据《红帽 Quay - 安装篇》完成 Quay 安装。 镜像代理缓存功能 Quay 的镜像代理缓存功能可以将用户拉取的远程镜像保存到本地 Quay 的 proxy cache 中&am…

阿里巴巴搜索API返回值:电商市场竞争的新武器含

阿里巴巴搜索API返回值在电商市场竞争中扮演着至关重要的角色,它为企业提供了深入了解市场、分析竞争对手的宝贵资源。以下是对阿里巴巴搜索API返回值及其在电商市场竞争中应用的详细解析,并附上示例代码。 一、阿里巴巴搜索API返回值概述 阿里巴巴搜索…

微波无源器件 功分器3 一种用于多端口辐射单元的紧凑四路双极化正交模功分器的设计

摘要: 一种有着双极化能力并且能作为一个Fabry-Perot谐振腔天线的馈源包含四个输入端口的新型紧凑功分器的概念和设计被提出了。在四个圆波导中的双同相极化通过使用四个5端口十字转门结合两个8by1(八合一) 功分网络。功分器末端接了两个端口…

排序----数据结构

Comparable Integer Double 默认情况下都是按照升序排列的 string 按照字母再ASCII码表中对应的数字升序进行排列 冒泡排序 时间复杂度O(x^2) 选择排序 时间复杂度O(x^2) 插入排序 时间复杂度O(x^2) 希尔排序 时间复杂度O(x) 归并排序 时间复杂度O(nlogn) 快速排序

一文搞定WeakHashMap

写在前面 在缓存场景下,由于内存是有限的,不能缓存所有对象,因此就需要一定的删除机制,淘汰掉一些对象。这个时候可能很快就想到了各种Cache数据过期策略,目前也有一些优秀的包提供了功能丰富的Cache,比如…

Pandas的入门操作-Series对象

Pandas的数据结构 Series对象 class pandas.Series(dataNone, indexNone) data参数 含义:data是Series构造函数中最主要的参数,它用来指定要存储在Series中的数据。 数据类型:data可以是多种数据类型,例如: Python 列…

JAVA基础,利用for循环找水仙花个数

public class learn2 {public static void main(String[] args) {int count 0;//定义水仙花的个数for (int i 100; i<999; i){int g i%10;int s i/10%10;int b i/100%10;if (i b*b*b s*s*s g*g*g){count1;System.out.println(i);}}System.out.println("一共有"…

LeetCode-137. 只出现一次的数字 II【位运算 数组】

LeetCode-137. 只出现一次的数字 II【位运算 数组】 题目描述&#xff1a;解题思路一&#xff1a;解题思路二&#xff1a;符号位一起判断。背诵版解题思路三&#xff1a;0 题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;除某个元素仅出现 一次 外&#xff0c;其余每…

渗透测试综合靶场 DC-2 通关详解

一、准备阶段 准备工具如Kali Linux&#xff0c;下载并设置DC-2靶场机。确保攻击机和靶机在同一网络段&#xff0c;通常设置为桥接模式或NAT模式。 1.1 靶机描述 Much like DC-1, DC-2 is another purposely built vulnerable lab for the purpose of gaining experience in …