重复性管理--抽象的重要性(上)

news2024/12/22 7:36:52

Haskell 语言的设计者之一 Paul Hudak 曾说过一句略带夸张的话(overstatement):

编程中最重要的三件事是: 抽象, 抽象, 抽象.

abstraction, abstraction, abstraction” are the three most important things in programming.

如果你去问一些资深开发者, 程序员最重要的的能力之一有哪些? 那么"抽象的能力"是绝对能排得上号的.

抽象的反面

那么什么是抽象呢? 抽象这个概念本身就是抽象的. 有时候, 如果不能很好地从正面把握它的含义, 不妨先从反面去考虑.

比如, 抽象的反义词是什么呢? 抽象的反面可以是具象, 也就是"具体的";另外, “抽象的"也可以对应"形象的”, 这些都可以算是它的反面.

跳出到编程之外

先不谈编程, 让我们把视野放得更开, 去看看一些现实中的例子.

一个数字的例子

让我们看一个图:

古巴比伦楔形数字, 罗马数字和中国数字的对比

这幅图中左边描述的古巴比伦楔形数字(Babylonian cuneiform numerals), 右边上面是罗马数字(Roman numerals), 下边就不用说, 我们自家的数字.

发现什么规律没有呢? 显然, 楔形数字 1-5 都是"形象的", 罗马和中国的数字前面三也是"形象的", 无非是一个是竖着来, 一个是横着来, 但到了 4,5 慢慢就变"抽象"了.

事实上, 汉字数字"四"还有一个异体字"亖", 也即下图所示, 也还挺"形象"的:

四的异体字

另一方面, 阿拉伯数字基本都很"抽象", 1,2,3,4,5, 彼此间并没有太多相似之处.

我们中国人常说"事不过三", 为什么许多数字系统从 4,5 开始就慢慢转向抽象了呢?

形象的好处及其代价

我估计大家都可能听说一个<<从三到万>>的故事:

汝有田舍翁, 家资殷盛, 而累世不识"之"“乎”. 一岁, 聘楚士训其子. 楚士始训之搦管临朱. 书一画训曰: 一字;书二画, 训曰: 二字;书三画, 训曰: 三字. 其子辄欣欣然, 掷笔归告其父, 曰: 儿得矣, 儿得矣;可无烦先生, 重费馆谷也, 请谢去. 其父喜, 从之, 具币谢遗楚士.

逾时, 其父拟征召姻友万氏者饮, 令子晨起治状, 久之不成. 父趣之, 其子恚曰: "天下姓氏夥矣, 奈何姓万!自晨起至今, 才完五百画也. "

呜呼!世之学者偶一解, 辄自矜有得, 殆类是也.

大意就是一个有钱的老头请一教书先生教儿子写字, 先生先教他的儿子执笔描红. 写一画, 教他说: "这是一字. "写二画, 教他说: "这是二字. "写三画, 教他说: "这是三字. “然后这个儿子就把笔一扔, 说这些太简单, 我学会了. 直到有一天, 他父亲让他给一个"万先生"写一个请帖, 结果他从清早写了半天, 也只完成"五百画”…

以上当然只是一个笑话, 不过从中我们也不难体会到, 形象虽然易于理解, 但一直这么形象下去, 弊端也是很明显的.

我们再看一个汉字"马"字的演变:

汉字"马"字的演变

也不难发现, 马一开始也是很形象的, 有鬃毛, 有四条腿乃至有尾巴, 细节非常丰富, 但到了后来这些细节就基本消失了. 毕竟, 如果你经常要写这么一个字, 而写这么个字简直跟画画差不多, 那肯定是非常累的一件事.

考虑到反复用时的方便, 形象化带来的麻烦已经超过带来的好处. 代价超过了好处, 经济学就要发挥它的威力了, 我们需要新的平衡.

基本上, 语言文字走的都是越来越抽象化的道路, 那些字母化的语言文字就跟不用说了.

回到数字的例子, 如果一直按照罗马数字开头的那种的方式构造, 那么数字 9 就会变成这样:

IIIIIIIII

猛一看上去, 还不好看出它到底是几竖. 而如果是 13 呢? 那就变成了这样:

IIIIIIIIIIIII

如果更大, 那就没救了, 甚至说, 它反而不形象了.

类似的一个例子可以参考之前的"复杂性管理与重复性管理".

一个文字的例子

说完了数字的例子, 再来看一个文字的例子.

如果有这么一段话:

"公司的营业额这个季度比上个季度增长 12%, 这个季度比去年这个季度增长 23%;公司的利润这个季度比上个季度增长 16%, 这个季度比去年这个季度增长 34%. "

显然, 这段话挺啰嗦的, 如果有更多的指标要描述, 则还会更啰嗦.

现在假如我们抽象出两个概念, 令:

环比=这个季度比上个季度

同比=这个季度比去年这个季度

那么我们可以把上述的话简化为:

"公司的营业额环比增长 12%, 同比增长 23%;公司的利润环比增长 16%, 同比增长 34%. "

是不是简洁了很多呢?

注: 严格地讲, 同比是指和上一周期的同时段相比;

环比是指和同一周期的上一个时段相比. 这里的时段可以是季度, 也可以是月;

周期类似, 一般是年, 但也可以是比年更大的(或更小).

尽管在这里, 环比和同比也还在不断重复, 但却是以一种较小代价在重复.

显然这种重复是没法避免的, 因为它是客观需求所决定的, 有多少个指标要描述, 我们就要重复多少遍.

直观地讲: 不断用更短的东西去取代更长的东西. 从"重复 9 或 11 个字"到只"重复 2 个字", 还是有进步的.

所以, 我们说要管理重复性, 当重复不可避免时, 关注的关键是怎样以较小代价去重复, 这就需要抽象, 把细节压制. 尽管对于刚接触这些抽象概念的人来说, 这种抽象性使他们一开始不容易明白, 但一旦熟悉了它们的具体含义, 以后就可以持续运用这些抽象概念并得到许多好处.

编程的例子

好了, 以上说了不少具体的事例, 如果你一开始对"抽象"这个概念还是迷迷糊糊的话, 现在应该有了一点感觉.

为什么举这么多例子呢? 本质上讲, 抽象就是从一个个具体的事例中"上升"或者说"升华"得到的, 我们的认知过程是从具体到抽象的.

我并不希望给"抽象"下一个抽象的定义给你, 我更希望你能通过这样一个个具体事例去感受什么是"抽象".

说完了编程外的, 现在让我们回到编程的例子上来.

一个具体的程序

假设要写一个排序的程序, 具体要求是这样的:

  1. 生成一个 10 个数字的随机数组(值 100 以内)

  2. 输出数组

  3. 对数组排序

  4. 输出排序后的数组

然后, 一个初学者他写的代码很可能是这样的:

未抽象之前的代码

注: 为紧凑起见, 省略了一些必要的换行及大括号.

那么, 这段代码有什么问题呢? 我们接下来一一分析一下.

如何建立抽象?

首先, 能很明显的观察到一个重复, 那就是"打印"部分, 这里实际缺乏一个名为 printArray 的抽象, 我们可以用静态方法来构建这么一个抽象,

注: 在 Eclipse 中, 可以这样操作:

  1. 选中"打印"部分的代码(上面或下面的均可)
  2. 选择"菜单—Refactor—Extract method…"
  3. 在弹出框中的"method name"一栏中输入"printArray"
  4. 点击 OK 即可完成这个重构

Eclispe 会为你找出两处重复的地方, 并一并替换它们, 在点击 OK 前, 也可以点击 Preview 先预览重构后的效果, IDEA 中也可以做类似操作, 具体细节此处从略.

结果如下:

抽象提取出打印的代码

我们建立了一个叫 printArray 的方法抽象, 接受一个数组作为参数, 然后在 main 方法中调用即可, 这样就减少了重复. 这就是用方法来构建抽象, 相信你也不会太陌生.

那么, 还能更进一步改进吗? 假如你有个同事听说你写了个排序的程序, 然后他手头有个数组也想排个序, 于是他想借用一些你的方法, 但你会很尴尬地发现你没有这样一个方法可以给到他.

虽然在你的代码中, 确实有一部分做了排序的工作, 可是你却没有明确地把这些代码提取出来, 它们和那些生成随机数, 打印的代码混在了一起, 以至于你的同事根本没有办法复用, 于是他很可能直接就把你的代码拷贝过去了, 也因此重复就产生了.

所以, 问题出在你的代码中没有抽象出一个叫"排序"的东西, 还是按照前面类似的重构步骤, 我们构建出一个叫 sort 的抽象:

抽象提取出排序的代码

缺省情况下, 重构出来的方法是 private, 这时你只要把它改成 public 的即可, 你的同事就可以复用它了, 这样他就可以避免拷贝你的代码或者重复去写另一个排序的代码, 所谓的"重复造轮子", 你们的系统中也因此避免了重复.

那么, 事情到了这里, 是否就结束了呢? 如果我们现在观察 main 方法,

混合的, 抽象与具体并存的 main 方法

我们有三个抽象的步骤, 但最前面还剩下一堆具体的代码, 显得跟下面的代码很不匹配.

假如这时又有一个同事说, 听说你写了一份生成随机数组的程序, 我也正好想要一份随机数组来做点事情, 我能用下你的方法吗?

然后你还是很尴尬…最后重复又产生了…

事实上这里还是缺乏一个抽象, 让我们再进一步, 构建一个叫 makeRandomArray 的抽象, 最终的效果如下:

抽象提取出创建随机数组的代码

如果现在我们回顾对比一下最初的需求以及我们最初的实现, 会发现需求的描述是抽象的, 比如并没有仔细描述应该如何去排序等, 但最初的实现却是非常具体的:

具体对抽象

如果把几行注释去掉, 甚至代码间彼此的边界也不清晰!而经过一番重构之后, 抽象的 4 条需求描述在我们的代码中有了非常明确的对应:

抽象对抽象

现在我们的 main 方法非常简洁, 逻辑也非常清晰, 甚至可以不需要注释.

过多的注释有时反而是一种代码的"坏味道"(bad smell), 说明你的代码的自解析性不强, 通常即是由缺乏抽象及良好命名所导致.

应该说, 能够做到这一步抽象的初学者是很少的.

甚至说有很多有多年经验的开发者也未必有这个意识, 他们可能会注意到 printArray 的重复, 也会抽象出一个 sort 方法, 但是否会为构建一个随机数组也做出一个抽象从而使其与整个需求步骤形成一个良好的匹配呢?

很多的教科书中的代码往往也是上来就是一堆的具体实现, 很少告诉读者其实在某些地方是可以适当引入一些抽象的.

有时我们甚至可以在 main 方法中就着需求直接先写出四个方法抽象, 再生成这些方法的空的骨架, 在这过程中我们只需关注输入输出, 然后再逐步填充方法的细节.

这也就是所谓的"top down"自顶向下式的编程, 关于这些, 可以参考之前的 小程序与大道理系列.

更进一步的抽象

对于 makeRandomArray 我们甚至还可以继续地抽象. 假如有同事说他想通过求一个随机数组的平均值来考察下生成的随机数的随机性, 然后他希望生成的数组能更大一点, 比如 30 个.

而我们现在的方法是写死了 10 个的, 那么他可能又被迫拷贝一份你的代码, 然后仅仅把其中的 10 改成 30, 这样显然又增加了很多的重复.

显然, 无论是 10 还是 30, 它们都是"具体的"值, 它们不够抽象, 这时我们可以把它参数化, 通过引入一个抽象的参数来取代这些具体的值,

这点也可以通过 Eclipse 中的重构实现:

  1. 先选中方法中那个具体的参数值 10
  2. 选择"菜单—Refactor—Introduce Parameter…"
  3. 在弹出框中输入参数名"size"
  4. 点击 OK

结果是这样:

参数化示例

这样你的代码又可以被别人所复用了, 他只要传一个不同的参数值即可, 系统也因此减少了重复.

现在假如又有一位同事说, 他想实现一个在前一千名的观众中随机抽取 20 位中奖者的程序, 那么现在的 makeRandomArray 方法能否满足呢?

显然, 生成 20 个随机数是没有问题的, 因为现在数组大小是抽象的, 参数化了的. 但一千名观众就有问题了, 因为的我们的代码中还有一个"具体的"值, 就是那个 100, 它阻碍了我们, 使得只能生成 100 以内的随机数.

某种意义上, 任何一个具体的东西都是一种限制, 使一段程序的通用性降低.

就好比说从"中国男人"到"男人"到"人", 它的抽象程度是不断提高的, 普适性是越来越强的.

“中国”, "男"这些都是具体的修饰, 也是一种限制.

如果我们想让程序具有较好的普适性, 就必须要将那些具体的东西干掉, 越抽象, 普适性就越好. 那么同样的, 我们可以将这个 100 用一个抽象的参数代替:

第二个参数化示例

那么你的同事也不用"重复发明轮子"(reinvent the wheel), 他只要发出这样一个不同实参的调用即可:

makeRandomArray(20, 1000)

如此, 你的系统又避免了重复. 一个变量, 一个参数它们都可以成为一种抽象.

关于参数化这个话题, 也可以参考之前的"重复性管理–从 泛值到泛型 以及 泛函 系列.

关于这个具体的例子, 就先谈论到这里. 由于篇幅关系, 一些更深入的探讨及更多的例子我们留待下篇再说.

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

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

相关文章

电商API接口教程|设计一个牛逼的Java API接口?

主要从以上三个方面来设计一个安全的电商API接口&#xff08;包含淘宝/天猫/京东/拼多多/抖音/跨境电商亚马逊/阿里巴巴/LAZADA等&#xff09;。 一 安全性问题 安全性问题是一个接口必须要保证的规范。如果接口保证不了安全性&#xff0c;那么你的接口相当于直接暴露在公网环…

vue3+klinecharts实现k线(附完整代码)

vue3klinecharts实现k线 环境安装图表配置创建一个容器实现点击时间切换图表完整代码 环境 vue3 klinecharts 最终实现 安装 npm install klinecharts图表配置 具体可用参考官网 https://klinecharts.com/guide/styles.html const option {crosshair: {show: true,//…

操作系统进程调度算法的模拟实现(c语言版本)

前言&#xff1a;本文旨在分享如何使用c语言对操作系统中的部分进程调度算法进行模拟实现&#xff0c;以及算法描述的讲解&#xff0c;完整代码放在文章末尾&#xff0c;欢迎大家自行拷贝调用 目录 常见的调度算法 数据结构 先来先服务调度算法 算法模拟思路&#xff1a; …

LeetCode刷题---简单组(三)

文章目录 &#x1f352;题目一 20. 有效的括号&#x1f352;解法一&#x1f352;题目二 26. 删除有序数组中的重复项&#x1f352;解法一&#x1f352;题目三 21. 合并两个有序链表&#x1f352;解法一 &#x1f352;题目一 20. 有效的括号 给定一个只包括 ‘(’&#xff0c;‘…

Q-learning智能体

Q-learning智能体 Q-learning算法是一种无模型、在线、非策略的强化学习方法。Q-learning算法是一种基于价值的强化学习算法&#xff0c;它训练一个评价网络去估计回报或未来奖励。对于给定的观测值&#xff0c;智能体选择并输出估计收益最大的动作。 注&#xff1a;Q-learnin…

PL/SQL工具下载地址

https://www.allroundautomations.com/registered-plsqldev/ 选择需要下载的版本即可

Shopee、Lazada卖家不得不看的提升销量技巧,自养号测评打造权重

近年来&#xff0c;大部分虾皮、Lazada卖家开始通过测评补单的方式来提升店铺权重和产品排名&#xff0c;以吸引更多流量。这种方式可以有效提高产品的销售转化率&#xff0c;对店铺的运营起到推动作用。然而&#xff0c;测评补单并非简单的购买过程&#xff0c;其中涉及到许多…

网页提示证书错误 怎么办

随着网站使用SSL证书来加密网站信息&#xff0c;SSL证书的应用也越来越广泛&#xff0c;那么有时候我们进入网站&#xff0c;明明安装了SSL证书&#xff0c;为什么也会提示证书错误呢&#xff1f; SSL证书错误可能是由以下原因导致的&#xff1a; 1. 证书过期&#xff1a; SSL证…

推荐一个假数据接口网页 适用于示例项目

DummyJSON - Fake REST API of JSON data for development

全面分享‘’找不到msvcp140.dll无法继续执行代码修复教程

计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“msvcp140.dll缺失”。这个错误通常会导致某些应用程序无法正常运行。为了解决这个问题&#xff0c;我们需要找到合适的解决办法。本文将介绍5种解决msvcp140.dll缺失问题的方法&#xff0c;帮…

【实战】硅基物语.AI写作高手:从零开始用ChatGPT学会写作

文章目录 从零开始使用ChatGPT学习写作一、简介二、ChatGPT写作的优势1. 提高效率2. 丰富的素材库3. 语言多样性4. 智能纠错 三、如何使用ChatGPT学习写作1. 确定写作目标和受众2. 了解ChatGPT的原理和特点3. 选择合适的模型和训练数据4. 制定写作计划和流程5. 不断实践和优化 …

管理员|顾问必看!8个Salesforce权限集的最佳实践

Salesforce中的权限一直始终是热门话题。权限集是简档的附加。它们通常具有相同的设置&#xff0c;用于增加用户的权限&#xff0c;使其超过简档提供的权限。可以将简档视为许多用户共有的基本权限集&#xff0c;而权限集是部分用户需要的额外权限。 本篇文章将介绍Salesforce…

Altium Designer元件库封装

资料 立创商城&#xff1a;https://www.szlcsc.com/ 立创EDA&#xff1a;https://pro.lceda.cn/ 库创建项目 项目创建 文件→新建→项目→PCB 新建原理图库 项目&#xff08;反键&#xff09;→添加新的…到工程→Schematic Library 新建PCB库 项目&#xff08;反键&…

【注意!水群需谨慎】手把手教你使用Python实时监测QQ群消息

文章目录 1. 写在前面2. 下载源码构建3. 通过release部署4. 运行本地服务5. 通过API获取数据6. 完整代码实现 1. 写在前面 前段时间写了一个QQ群消息监测的自动化机器人&#xff0c;需求则是加入到某些特定的群组&#xff0c;对群内对话消息进行实时监测与分析。当然&#xff0…

uniapp:谷歌地图,实现地图展示,搜索功能,H5导航

页面展示 APP H5 谷歌地图功能记录,谷歌key申请相对复杂一些,主要需要一些国外的身份信息。 1、申请谷歌key 以下是申请谷歌地图 API 密钥的流程教程: 登录谷歌开发者控制台:打开浏览器,访问 Google Cloud Platform Console。 1、创建或选择项目:如果你还没有创建项目…

智能低代码洪流涌动程序员节 华为云Astro触发1024的乘法效应

从人工智能至量子计算&#xff0c;再到最新的云原生技术&#xff0c;越来越多的荣耀被程序员斩获。今年1024程序员节&#xff0c;华为云Astro向全民致敬&#xff1a;「低代码高产出 拓荒数字化版图——人人皆是程序员」&#xff0c;为全球工程师及开发爱好者呈现智能化开发的魅…

北京筑龙发声炼化企业大会,助力央国企采购供应链数字化转型

10月25日&#xff0c;以“科技创新引领高质量发展&#xff0c;夯实炼化自立自强根基”为主题的第四届炼化企业创新发展大会暨新技术与解决方案交流会”在浙江省宁波市盛大召开。北京筑龙智能化业务部高级咨询顾问王良受邀出席&#xff0c;带来主题为“智能物料——企业采购供应…

FreeRTOS学习day1任务创建(动态创建)

顾名思义 免费的实时操作系统 用法基本和Linux下的多线程编程类似 探索者开发版实验 动态创建4个任务start_task task1 task2 task3 优先级依次为1 2 3 4 &#xff08;注意优先级不能为0,0是空闲任务&#xff09; 我的理解&#xff1a;主线程start_task 主线程 task1 ta…

uniapp接口请求api封装,规范化调用

封装规范和vue中的差不多&#xff0c;都是统一封装成一个request对象&#xff0c;然后在api.js里面调用。 先创建一个utils文件夹&#xff0c;然后里面创建一个request.js&#xff0c;代码如下&#xff1a; export const baseURL 基础url地址const request (options) > …

k8s-----25、资源调度-ResourceQuota资源配额、资源限制limitrange、服务质量QoS

1、ResourceQuota资源配额 1.0 作用 命名空间资源配额。防止公司内部人员对资源的不合理利用。 1.1、为什么需要资源配额 1、作为k8s集群的管理员&#xff0c;知道集群的规模&#xff0c;会合理规划资源&#xff0c;但是使用侧不知道&#xff0c;会导致很多不合理的使用场景…