让整洁代码指引你吧,然后忘了它

转自:https://overreacted.io/zh-hans/goodbye-clean-code/

那是一个深夜。

我的同事们刚刚提交了过去整整一周所写的代码。我们正在开发一个基于 Canvas 的图形编辑器,他们负责实现各种“形状”(比如:矩形、椭圆形)的缩放功能,缩放行为由拖拽“形状”边缘上的操作柄来实现。

代码运行正常。

但是代码有些重复。每个“形状”(如:矩形、椭圆形)各自拥有若干操作柄,从不同方向拖拽操作柄都会对“形状”的位置和尺寸产生相应的影响。如果用户按住“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 上的其它方法是类似的。这是因为,它们都是在处理“椭圆形”上的问题。更进一步,Rectangle, Header 以及 TextBlock 都有相似的地方,因为文本框都是“矩形”。

我有个办法。

我们可以通过对代码逻辑进行分组,来实现“去除全部重复”的目的:

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);

代码量减少到 1 / 2,并且,重复代码不复存在!这真是“整洁”啊。如果我们希望改变具体某个“方向”上的或“形状”上的行为,只需要修改一处,而非多处。

已经很晚了(我很疲惫了),我提交我重构后的代码,然后就躺下了,并对自己“帮助同事整洁代码”而感到心满意足。

第二天上午

… 事情并非我想的那样。

我的上级单独找到了我,委婉地要求我撤销昨天的那些修改。我感到很不可思议,旧代码那么差劲,而我的是多么“整洁”啊!

我那时只好谨遵照守;但是后来,历经数年,我才明白他们的要求是对的。

那是一个职业阶段

对“整洁代码”的沉迷、好于“消除重复代码”,是我们大多数开发者都会经历的阶段。当我们对自己的代码没有信心,自我价值感和专业荣誉感往往会使我们与一些可测量的东西靠拢,一组严格的 lint 规则、一套命名规范、一种文件结构、无重复性指标,等等。

你不可能让“重复代码”自动被消除,但是确实可以借助“实践经验”使它更可行。你往往可以看到,每次修改后的代码量是变得更多或更少。结果就是,消除重复代码看起来提高了代码的某些客观的可测量指标。然而,糟糕的是,这扰乱了人们的自我认知,他们会对自己说:“我是那种会写出整洁代码的人”,而这其实跟其它的自我欺骗行为没什么两样。

一旦学会了如何“抽象”,我们似乎就会更进一步,每次看到重复的代码,我们甚至能够从“无”中发现可“抽象”的东西。编码数年以后,我们已经厉害得随处都能看到重复的代码,我们同时获得了一项新的超能力:抽象。进而,如果有人告诉我们“抽象是好的”,我们便对此更加笃信。于是我们开始去批判那些不崇尚"整洁代码"的人。

我现在明白,我的那次“重构行为”确实是一场灾难(译者:夸张了吧~),从两个方面来说:

  • 首先,我没有告诉代码的作者。我在没有他们参入的情况下,重写并提交了代码。即便,那确是一次对代码的“改善”(我现在不以为然了),它也十分可怕。一个健康的工程团队需要不断地“建立彼此的信任”。不经讨论而擅自重写同事的代码,是对高效协作能力的彻底否定。

  • 其次,一切都需要权衡。为了减少重复,我的代码难于应付需求变更,而这并不值得。例如,我们后边需要为不同“形状”上的各个操作柄添加特殊的行为逻辑。为了应付这些,我的“抽象”无疑使修改困难了数倍,反观那原本“肮脏的”代码,它则能够轻松处理。

我的意思是你应该写“脏”的代码吗?不,我建议你深入思考一下“整洁”与“肮脏”分别意指何物。你有对于批判、正确、美和优雅的直觉吗?你如何能确定地说你的这次工程成果达到了那些质量指标?它们如何确切地影响到代码的产生与维护

我当时确然对于这些问题没有进行过深入思考;我当时考虑的是代码“看起来”如何,而不是代码该如何随着不断流动的团队而演进。

写代码是一场旅行。回想一下,你编写第一行代码时的情形,到现在你所处的状态。我相信,当突然发现“抽取出一个函数”或“重构一个类的定义”是如何使问题由复杂变得简单的时候,你必定是开心的。一旦对自己的作品产生了自豪感,你就会热切地去追求代码的整洁。对此,你可以保持一段时间。

但是不要止乎此,不要成为一个整洁代码的狂热分子。整洁代码并不是目的,它只是让我们从所面对系统的异常复杂性中解脱出来的方法。在你不是很明确一个改动会如何影响到整个代码库时,这信念可以作为一种安全防护机制。但是在未知的海洋里,你需要一个指南针。

那就让整洁代码指引你吧,然后忘了它

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值