代码整洁?我后悔重构了代码

在这里插入图片描述

原文:Dan Abramov - 2020.01.11

那是一个深夜。

我的同事刚刚提交了他们一周编写的代码。我们正在开发一个图形编辑器的画布,他们实现了通过拖动边缘的小手柄,来调整形状(如矩形和椭圆)的大小的功能。

代码是有效的。

但是,它有些重复。每种形状(如矩形或椭圆)都有一组不同的手柄,每个手柄在不同的方向上拖动,会以不同的方式影响形状的位置和大小。如果用户按住 Shift 键,我们还需要在调整大小的同时保持比例。这里涉及到一堆数学计算。

代码看起来像这样:

// 矩形
let Rectangle = {resizeTopLeft(position, size, preserveAspect, dx, dy) {// 10 repetitive lines of math},resizeTopRight(position, size, preserveAspect, dx, dy) {// 10 repetitive lines of math},resizeBottomLeft(position, size, preserveAspect, dx, dy) {// 10 repetitive lines of math},resizeBottomRight(position, size, preserveAspect, dx, dy) {// 10 repetitive lines of math},
};// 椭圆
let Oval = {resizeLeft(position, size, preserveAspect, dx, dy) {// 10 repetitive lines of math},resizeRight(position, size, preserveAspect, dx, dy) {// 10 repetitive lines of math},resizeTop(position, size, preserveAspect, dx, dy) {// 10 repetitive lines of math},resizeBottom(position, size, preserveAspect, dx, dy) {// 10 repetitive lines of math},
};let Header = {resizeLeft(position, size, preserveAspect, dx, dy) {// 10 repetitive lines of math},resizeRight(position, size, preserveAspect, dx, dy) {// 10 repetitive lines of math},
};let TextBlock = {resizeTopLeft(position, size, preserveAspect, dx, dy) {// 10 repetitive lines of math},resizeTopRight(position, size, preserveAspect, dx, dy) {// 10 repetitive lines of math},resizeBottomLeft(position, size, preserveAspect, dx, dy) {// 10 repetitive lines of math},resizeBottomRight(position, size, preserveAspect, dx, dy) {// 10 repetitive lines of math},
};

这种重复的数学计算真的让我很困扰。

它并不整洁

大部分的重复是在相似的方向之间。例如,Oval.resizeLeft()Header.resizeLeft() 有相似之处。这是因为它们都处理了在左侧拖动手柄的情况。

另一种相似性是在同一形状的方法之间。例如,Oval.resizeLeft() 与其他 Oval 方法有相似之处。这是因为它们都处理了椭圆。在 RectangleHeaderTextBlock 之间也有一些重复,因为文本块就是矩形。

因此,我有一个想法。

我们可以通过如下的方式消除所有重复,将代码分组:

// 方向
let Directions = {top(...) {// 5 unique lines of math},left(...) {// 5 unique lines of math},bottom(...) {// 5 unique lines of math},right(...) {// 5 unique lines of math},
};// 形状
let Shapes = {Oval(...) {// 5 unique lines of math},Rectangle(...) {// 5 unique lines of math},
}

然后组合它们的行为:

let { top, bottom, left, right } = Directions;function createHandle(directions) {// 20 lines of code
}let fourCorners = [createHandle([top, left]),createHandle([top, right]),createHandle([bottom, left]),createHandle([bottom, right]),
];
let fourSides = [createHandle([top]),createHandle([left]),createHandle([right]),createHandle([bottom]),
];
let twoSides = [createHandle([left]), createHandle([right])];function createBox(shape, handles) {// 20 lines of code
}let Rectangle = createBox(Shapes.Rectangle, fourCorners);
let Oval = createBox(Shapes.Oval, fourSides);
let Header = createBox(Shapes.Rectangle, twoSides);
let TextBox = createBox(Shapes.Rectangle, fourCorners);

代码的总量减半,重复的部分完全消失了!它是如此整洁。如果想改变某个方向或形状的行为,我们可以在一个地方进行修改,而不是在各处更新方法。

已经是深夜了(我有点过于投入)。我将重构代码提交到 master 分支,然后满怀自豪地上床睡觉,因为我解开了同事混乱的代码。

第二天早上

…并不像我预期的那样。

我的老板邀请我进行一对一的聊天,他礼貌地要求我撤销昨夜的更改。我感到震惊,旧的代码一团糟,而我的代码整洁

我勉强同意了,但我花了好几年的时间才看出他们是对的。

这只是一个阶段

痴迷于“整洁的代码”和消除重复是我们许多人都会经历的阶段。当我们对自己的代码没有信心时,我们很容易将自我价值和职业骄傲寄托在一些可以衡量的东西上。一套严格的 lint 规则,一个命名方案,一个文件结构,没有重复代码。

你不能自动消除重复,但随着实践的增加,这确实会变得更容易。你通常可以看出每次更改后重复的部分是增加还是减少。因此,消除重复感觉就像是改善了代码的某种客观指标。更糟糕的是,它干扰了人们的身份认同感:“我就是那种写整洁代码的人”。这就像任何种类的自我欺骗一样有力。

一旦我们学会如何创建抽象,我们就会很容易对这种能力产生依赖,每当看到重复的代码,就会凭空提出抽象。编程几年后,我们看到重复无处不在——抽象是我们的新超能力。如果有人告诉我们抽象是一种美德,我们会全盘接受,甚至会开始评判其他人为什么不崇尚“整洁”。

我现在明白我的“重构”在两个方面都是灾难性的:

  • 首先,我没有和写这段代码的人交谈。我重写了代码,没有他们的参与就提交了。即使这一个改进(我现在不再这么认为),这也是一个糟糕的做法。一个健康的工程团队需要不断建立信任。在没有讨论的情况下重写你同事的代码,会严重打击你们在代码库上有效协作的能力。
  • 其次,没有什么是免费的。我的代码以减少重复为代价,牺牲了改变需求的能力,这是不值得的。例如,我们后来需要为不同形状的不同手柄添加许多特殊情况和行为。我的抽象需要变得更加复杂才能实现这些,而在原始的“混乱”版本中,这样的更改则易如反掌。

我是在说应该写“脏”代码吗?不是。我建议深入思考你说“整洁”或“脏”时的含义。有一种反感的感觉吗?正义感?美感?优雅感?你有多确定可以列出对应于这些品质的具体工程结果?它们如何确切地影响代码的编写和修改?

我肯定没有深入思考过这些问题。我考虑了很多关于代码看起来如何 —— 但并没有考虑它如何在一个由复杂多变的人组成的团队中发展

编程是一场旅程。想想你从编写第一行代码到现在走过的路程。我想,第一次看到提取函数或重构类可以让复杂的代码变得简单,一定是一种快乐的体验。如果你对自己的技术感到自豪,那么就会很容易追求代码的整洁性。那就去追求吧。

但不要止步于此。不要成为一个整洁代码的狂热者。整洁的代码不是终极目标,它是我们试图理解我们所处理的巨大复杂系统的一种尝试。当你还不确定一个更改会如何影响代码库,但你在未知的混沌中需要指引时,它是一种防御机制。

让整洁的代码引导你,然后放手。

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

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

相关文章

实习僧网站的实习岗位信息分析

目录 背景描述数据说明数据集来源问题描述分析目标以及导入模块1. 数据导入2. 数据基本信息和基本处理3. 数据处理3.1 新建data_clean数据框3.2 数值型数据处理3.2.1 “auth_capital”(注册资本)3.2.2 “day_per_week”(每周工作天数&#xf…

TFT显示屏驱动

REVIEW 已经学习过VGA 时序与实现-CSDN博客 VGA 多分辨率-CSDN博客 今天就来让TFT屏显示一下 ACZ702开发板管脚信息表 - ACZ702开发板 - 芯路恒电子技术论坛 - Powered by Discuz! (corecourse.cn) 小梅哥视频:24 RGB TFT显示屏原理与驱动实现_哔哩哔哩_bilibili …

活动图高阶讲解-16

77 00:05:39,520 --> 00:05:41,520 另外一个就是循环 78 00:05:41,520 --> 00:05:45,520 如果怎么样 79 00:05:45,520 --> 00:05:47,520 就再做一遍 80 00:05:47,520 --> 00:05:49,520 如果还满足条件就再做一遍 81 00:05:49,520 --> 00:05:51,520 那就是循…

TG-12F使用SDK对接阿里生活物联网平台

文章目录 前言一、注意二、准备1. 安装Ubuntu(版本20.04 X64)程序运行时库。按顺序逐条执行命令:2. 安装Ubuntu(版本20.04 X64)依赖软件包。按照顺序逐条执行命令:3. 安装Python依赖包。按照顺序逐条执行命…

[spring] Spring Boot REST API - CRUD 操作

Spring Boot REST API - CRUD 操作 这里主要提一下 spring boot 创建 rest api,并对其进行 CRUD 操作 jackson & gson 目前浏览器和服务端主流的交互方式是使用 JSON(JavaScript Object Notation),但是 JSON 没有办法直接和 Java 的 POJO 创建对应…

python-numpy(3)-线性代数

一、方程求解 参考资料 对于Ax b 这种方程: np.linalg.inv(A).dot(B)np.linalg.solve(A,b) 1.1 求解多元一次方程一个直观的例子 # AXB # X A^(-1)*B A np.array([[7, 3, 0, 1], [0, 1, 0, -1], [1, 0, 6, -3], [1, 1, -1, -1]]) B np.array([8, 6, -3, 1]…

cannot import name ‘get_host‘ from ‘urllib3.util.url‘

Error in py_module_import(module, convert convert) : ImportError: cannot import name get_host from urllib3.util.url (D:\\url.py) Run reticulate::py_last_error() for details. 这个错误表明在 urllib3 模块的 util.url 子模块中找不到名为 get_host 的函数。这可能…

第十五届蓝桥杯省赛C/C++大学B组真题及赛后总结

目录 个人总结 C/C 组真题 握手问题 小球反弹 好数 R 格式 宝石组合 数字接龙 爬山 拔河 ​编辑 再总结及后续规划 个人总结 第一次参加蓝桥杯,大二,以前都在在学技术,没有系统的学过算法。所以,还是花了挺多时间去备…

Rust - 所有权

所有的程序都必须和计算机内存打交道,如何从内存中申请空间来存放程序的运行内容,如何在不需要的时候释放这些空间,成了重中之重,也是所有编程语言设计的难点之一。在计算机语言不断演变过程中,出现了三种流派&#xf…

基于深度学习的花卉检测系统(含PyQt界面)

基于深度学习的花卉检测系统(含PyQt界面) 前言一、数据集1.1 数据集介绍1.2 数据预处理 二、模型搭建三、训练与测试3.1 模型训练3.2 模型测试 四、PyQt界面实现参考资料 前言 本项目是基于swin_transformer深度学习网络模型的花卉检测系统,…

软考125-上午题-【软件工程】-传统软件的测试策略

一、传统软件的测试策略 有效的软件测试实际上分为4步进行,即:单元测试、集成测试、确认测试、系统测试。 1-1、单元测试(模块测试) 单元测试也称为模块测试,在模块编写完成且无编译错误后就可以进行。 单元测试侧重…

温故知新之-TCP Keepalive机制及长短连接

[学习记录] 前言 TCP连接一旦建立,只要连接双方不主动 close ,连接就会一直保持。但建立连接的双方并不是一直都存在数据交互,所以在实际使用中会存在两种情况:一种是每次使用完,主动close,即短连接&…

JVM虚拟机(五)强引用、软引用、弱引用、虚引用

目录 一、强引用二、软引用三、弱引用四、虚引用五、总结 引文: 在 Java 中一共存在 4 种引用:强、软、弱、虚。它们主要指的是,在进行垃圾回收的时候,对于不同的引用垃圾回收的情况是不一样的。下面我们就一起来看一下这 4 种引用…

51单片机实验03-单片机定时/计数器实验

目录 一、实验目的 二、实验说明 1、51单片机有两个16位内部计数器/定时器(C/T, Counter/Timer)。 2、模式寄存器TMOD 1) M1M0工作模式控制位; 2) C/T定时器或计数器选择位: 3)GATE定时器/计数器运行…

软考系规第2章思维导图,软硬件网络和次新技术大杂烩

虽然目前系统规划与管理师的教程是否改版存在不确定性,但是不影响咱们先概要了解当前的教程,使用思维导图的方式粗读教程。 为了帮助你更好的学习系规教程,降低系规教程阅读门槛,指尖疯特发起了教程伴读活动,通过伴读脑…

关于GDAL计算图像坐标的几个问题

关于GDAL计算图像坐标的几个问题_gdal读取菱形四角点坐标-CSDN博客 这篇文章写的很好,讲清楚了图像行列号与图像点坐标(x,y)对应关系,以及图像行列号如何转为地理坐标的,转载一下做个备份。 1.关于GDAL计算图像坐标的…

部署Kafka集群图文详细步骤

1 集群规划 共三台虚拟机同处overlay网段,每台虚拟机部署一套kafka和zookeeper,kafka_manager安装其中一台虚拟机上即可。 HostnameIP addrPortListenerzk1docker-swarm分配2183:2181zk2docker-swarm分配2184:2181zk3docker-swarm分配2185:2181k1docke…

python-使用bottle时间简易服务器

python-使用bottle时间简易服务器 背景调试读取文本所有内容字段解释json字符串解析追加写入文件 整理后整理后写入文件方法将目录下所有文本的内容批量追加到一个文本搜索字符串方法实现简易服务器通过浏览器访问 背景 202310.txt内容是一段json字符串,目的是通过…

C++进阶技巧:如何在同一对象中存储左值或右值

如何在同一对象中存储左值或右值 一、背景二、跟踪值2.1、存储引用2.2、存储值 三、存储variant四、通用存储类4.1、定义const访问4.2、定义非const访问 五、创建存储六、总结 一、背景 C 代码似乎经常出现一个问题:如果该值可以来自左值或右值,则对象如…

Arrow, 一个六边形的 Python 时间库

文章目录 Arrow, 一个六边形的 Python 时间库第一部分:背景介绍第二部分:库是什么?第三部分:如何安装这个库?第四部分:库函数使用方法第五部分:场景应用第六部分:常见Bug及解决方案第…