清洁架构:遥不可及的理想——给开发者的寓言

清洁架构:遥不可及的理想——给开发者的寓言

在西藏宁静的山区高处,一座古老寺院安静的大厅里,住着一位年轻的学徒。他致力于追求和谐——不仅在他自己内部,而且在他的编程技巧中。他的目标是创建一个完美的应用程序,一个能够体现清洁架构深刻原理的应用程序,就像山间溪流的清澈一样。但他意识到这条道路的艰辛,于是向一位可敬的明师寻求智慧。

徒弟谦卑地走近师父问道

—“噢,明智的师父,我已经构建了一个应用程序来管理购买。我的建筑干净纯粹吗?”

师父耐心地观察弟子,回答道:

—“向我展示你所创造的东西,我们将一起辨别真相。”

学徒展示了他的作品,其中数据库逻辑和用例流程交织在一起——业务逻辑与技术框架紧密地交织在一起,就像一张错综复杂的网中的线。

// app.tsimport sqlite3 from 'sqlite3';import { open, database } from 'sqlite';interface purchase {    id: number;    title: string;    cost: number;}async function initializedatabase(): promise {    const db = await open({        filename: ':memory:',        driver: sqlite3.database,    });    await db.exec(`    create table purchases (      id integer primary key,      title text,      cost real    )  `);    return db;}async function addpurchaseifcan(db: database, purchase: purchase): promise {    const { id, title, cost } = purchase;    const row = await db.get(        `select sum(cost) as totalcost from purchases where title = ?`,        [title]    );    const totalcost = row?.totalcost || 0;    const newtotalcost = totalcost + cost;    if (newtotalcost  {    const db = await initializedatabase();    await addpurchaseifcan(db, { id: 3, title: 'rice', cost: 2 });})();

大师思考代码后说道:

——“你的代码就像一条河流,目的的清水与实现的泥浆混合在一起。业务和技术问题本应分开进行,但它们已合而为一。为了在你的建筑中实现真正的纯粹,你必须将它们分开,就像天空与大地分开一样。”

道路上的第一步

听从师父的话,徒弟开始着手重构他的代码。他开始分离各层,在数据库和业务逻辑流之间划出明显的界限。他还引入了接口,使他的代码与依赖倒置原则保持一致,这是清洁架构的神圣教义之一。现在,他的应用不再依赖于具体的实现,而是依赖于思想的抽象。

// app.tsimport { initializedatabase } from './db/init';import { purchaserepository } from './db/purchaserepository';import { addpurchaseifcan } from './usecases/addpurchaseifcan';(async () => {  const db = await initializedatabase();  const purchaserepository = new purchaserepository(db);  await addpurchaseifcan(purchaserepository, { id: 3, title: 'rice', cost: 2 });})();
// usecases/addpurchaseifcan.tsimport { ipurchaserepository, purchase } from './ipurchaserepository';export async function addpurchaseifcan(  purchaserepository: ipurchaserepository,  purchase: purchase): promise {  const { id, title, cost } = purchase;  const totalcost = await purchaserepository.gettotalcostbytitle(title);  const newtotalcost = totalcost + cost;  if (newtotalcost < 99999) {    await purchaserepository.add(purchase);    console.log('purchase added successfully.');  } else {    console.log('total cost exceeds 99999.');  }}
// usecases/ipurchaserepository.tsexport interface ipurchaserepository {  add(purchase: purchase): promise;  gettotalcostbytitle(title: string): promise;}export interface purchase {  id: number;  title: string;  cost: number;}
// db/init.tsimport sqlite3 from 'sqlite3';import { open, database } from 'sqlite';export async function initializedatabase(): promise {  const db = await open({    filename: ':memory:',    driver: sqlite3.database,  });  await db.exec(`    create table purchases (      id integer primary key,      title text,      cost real    )  `);  return db;}
// db/purchaserepository.tsimport { database } from 'sqlite';import { ipurchaserepository, purchase } from 'usecases/ipurchaserepository';export class purchaserepository implements ipurchaserepository {  private db: database;  constructor(db: database) {    this.db = db;  }  async add(purchase: purchase): promise {    const { id, title, cost } = purchase;    await this.db.run(      `insert into purchases (id, title, cost) values (?, ?, ?)`,      [id, title, cost]    );    return purchase;  }  async gettotalcostbytitle(title: string): promise {    const row = await this.db.get(      `select sum(cost) as totalcost from purchases where title = ?`,      [title]    );    const totalcost = row?.totalcost || 0;    return totalcost;  }}

徒弟回到师傅并问道:

——“我已经为我的存储库分离了各层并使用了接口。我的建筑现在干净了吗?”

大师再次检查代码,回复:

——“你们已经向前迈出了一步,但总成本的计算仍然停留在基础设施上,它不属于那里。这不是这种智慧应该存在的地方。总成本的知识属于商业领域,而不是地球上的工具。将其移至用例中,过程的智慧可以保持其纯粹性。”

分离的教训

有了这种洞察力,学徒意识到总成本的计算是业务逻辑的一部分。他再次重构了代码,将逻辑转移到用例中,使业务问题不受技术基础设施的影响。

// usecases/ipurchaserepository.tsexport interface ipurchaserepository {  add(purchase: purchase): promise;-  gettotalcostbytitle(title: string): promise;+  getpurchasesbytitle(title: string): promise;}...
// usecases/addpurchaseifcan.tsimport { ipurchaserepository, purchase } from './ipurchaserepository';export async function addpurchaseifcan(  purchaserepository: ipurchaserepository,  purchasedata: purchase,  limit: number): promise {  const { id, title, cost } = purchasedata;  const purchases = await purchaserepository.getpurchasesbytitle(title);  let totalcost = 0;  for (const purchase of purchases) {    totalcost += purchase.cost;  }  const newtotalcost = totalcost + cost;  if (newtotalcost >= limit) {    console.log(`total cost exceeds ${limit}.`);  } else {    await purchaserepository.add(purchasedata);    console.log('purchase added successfully.');  }}
// db/purchaserepository.tsimport { database } from 'sqlite';import { ipurchaserepository } from './ipurchaserepository';export class purchaserepository implements ipurchaserepository {  ...  async getpurchasesbytitle(title: string): promise {    const rows = await this.db.all(      `select * from purchases where title = ?`,      [title]    );    return rows.map((row) => ({      id: row.id,      title: row.title,      cost: row.cost,    }));  }}

再次回到师父身边,问道:

—“我已将总成本计算转移到用例中,并将业务逻辑与基础设施分开。我的建筑现在纯净了吗?”

师父带着温柔的微笑回答:

——“你已经取得了很大的进步,但要小心——就像山风带来冬天的寒冷一样,你的计算可能会带来隐藏的错误。 javascript 的算术就像新手的思维一样,在处理大数或小数时可能会不精确。”

与无常的相遇

学徒明白javascript中浮点运算的缺陷可能会导致微妙但危险的错误。他修改了代码,转向更可靠的工具,一个专为精确计算而设计的库,寻求工作的清晰度。

// usecases/addpurchaseifcan.ts+ import decimal from 'decimal.js';import { ipurchaserepository, purchase } from './ipurchaserepository';export async function addpurchaseifcan(  purchaserepository: ipurchaserepository,  purchasedata: purchase,  limit: number): promise {  const { id, title, cost } = purchasedata;  const purchases = await purchaserepository.getpurchasesbytitle(title);  let totalcost = new decimal(0);  for (const purchase of purchases) {-    totalcost += purchase.cost;+    totalcost = totalcost.plus(purchase.cost);  }- const newtotalcost = totalcost + cost;+ const newtotalcost = totalcost.plus(cost);- if (newtotalcost >= limit) {+ if (newtotalcost.greaterthanorequalto(limit)) {    console.log(`total cost exceeds ${limit}.`);  } else {    await purchaserepository.add(purchasedata);    console.log('purchase added successfully.');  }}

他再次问师父:

—“我已经改进了我的计算,使用了更好的工具来避免错误。我的建筑现在达到纯粹了吗?”

上人目光坚定如山,答道:

——“你做得很好,但你的架构仍然受到束缚。您的业​​务逻辑现在取决于这个新工具decimal.js的详细信息。如果有一天你需要改变这个工具,你的逻辑基础就会动摇。真正的纯洁是摆脱这种束缚。”

依赖倒置的智慧

认识到大师话语的深度,学徒试图将他的代码从这种执着中解放出来。他抽象了算术运算,颠倒了依赖关系,这样他的业务逻辑就不再依赖于任何一种工具。

// usecases/calculator.tsexport abstract class calculator {  abstract create(a: number): calculator;  abstract add(b: calculator | number): calculator;  abstract greaterthanorequal(b: calculator | number): boolean;}
// usecases/addpurchaseifcan.ts+ import { calculator } from 'usecases/calculator';- import decimal from 'decimal.js';import { ipurchaserepository, purchase } from './ipurchaserepository';
// decimalcalculator.tsimport decimal from 'decimal.js';import { calculator } from 'usecases/calculator.ts';export class decimalcalculator extends calculator {  private value: decimal;  constructor(value: number | decimal) {    super();    this.value = new decimal(value);  }  create(a: number): calculator {    return new decimalcalculator(a);  }  add(b: calculator | number): calculator {    return new decimalcalculator(this.value.plus(b.value));  }  greaterthanorequal(b: calculator | number): boolean {    return this.value.greaterthanorequalto(b.value);  }}
// useCases/addPurchaseIfCan.tsimport { Calculator } from 'useCases/calculator';import { IPurchaseRepository, Purchase } from './IPurchaseRepository';export class addPurchaseIfCan {  private purchaseRepository: IPurchaseRepository;  private calculator: Calculator;  private limit: string;  constructor(    purchaseRepository: IPurchaseRepository,    calculator: Calculator,    limit: number  ) {    this.purchaseRepository = purchaseRepository;    this.calculator = calculator;    this.limit = limit.toString();  }  async execute(purchaseData: Purchase): Promise {    const { id, title, cost } = purchaseData;    const purchases = await this.purchaseRepository.getPurchasesByTitle(title);    let totalCost = this.calculator.create(0);    for (const purchase of purchases) {      totalCost.add(purchase.cost);    }    totalCost = totalCost.add(cost);    if (totalCost.greaterThanOrEqual(this.limit)) {      console.log(`Total cost exceeds ${limit}.`);    } else {      await this.purchaseRepository.add({        id,        title,        cost: parseFloat(cost.toString()),      });      console.log('Purchase added successfully.');    }  }}

最后一次回到师父身边,他问道:

——“我使用依赖倒置抽象了我的操作,确保我的业务逻辑不再与实现绑定。我的建筑现在真的干净了吗?”

师父开示:

——“这条路上你已经走得很远了。但请记住,即使您努力追求纯度,您的用例仍然取决于编写它们的语言。您现在使用的是 javascripttypescript,但有一天这些工具可能会消失。当那一天到来时,你会用新的语言重建一切吗?”

拥抱不完美

学徒对此感到困惑,问道:

——“大师,如果我的用例总是与编写它们的语言联系在一起,我怎样才能在我的架构中实现完美的整洁?”

师父带着理解的柔和微笑回答:

——“就像鸟儿无法离开天空一样,建筑也不能完全脱离其创作的工具。真正的独立是一个崇高的梦想,但却是遥不可及的。然而,对它的追求会给你的建筑带来和谐。 清洁架构的目标不是摆脱所有依赖性,而是创建一个轻松应对变化的系统,并将商业智慧与地球运作分开。理解这种平衡是获得真正智慧的关键。”

学徒,感受到他内心的理解黎明之光,说道:

——“谢谢师父。现在我发现完美不是孤立的,而是责任和目标的和谐。”

师父从座位上站起来,回答:

——“放心吧,学徒。你的旅程才刚刚开始,但你已经找到了自己的路。”

结语

随着时间的流逝,学徒注意到他的应用程序开始变慢。他很困惑,想知道一个曾经运行得如此顺利的系统现在如何在执行任务时陷入困境。

很明显,问题不在于代码的大小不断增长,而在于总成本计算是在数据库外部进行的。该应用程序花费了大量的精力来传输大量数据,只是为了执行本可以在源头完成的任务。如果计算是在数据库内完成的,则无需在层之间发送数千条记录,并且性能仍将保持强劲。

徒弟想向师父询问此事,但师父已经消失,问题一直没有答案。

望着寂静的寺院,徒弟拿起一本新书,微笑着说道:

—“看来我的启蒙之路给我带来了新的挑战 – 性能优化的艺术。”

以上就是清洁架构:遥不可及的理想——给开发者的寓言的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1493240.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月19日 15:31:36
下一篇 2025年12月19日 15:31:40

相关推荐

  • 旋转长方形后,如何计算其相对于画布左上角的轴距?

    绘制长方形并旋转,计算旋转后轴距 在拥有 1920×1080 画布中,放置一个宽高为 200×20 的长方形,其坐标位于 (100, 100)。当以任意角度旋转长方形时,如何计算它相对于画布左上角的 x、y 轴距? 以下代码提供了一个计算旋转后长方形轴距的解决方案: const x = 200;co…

    2025年12月24日
    000
  • 旋转长方形后,如何计算它与画布左上角的xy轴距?

    旋转后长方形在画布上的xy轴距计算 在画布中添加一个长方形,并将其旋转任意角度,如何计算旋转后的长方形与画布左上角之间的xy轴距? 问题分解: 要计算旋转后长方形的xy轴距,需要考虑旋转对长方形宽高和位置的影响。首先,旋转会改变长方形的长和宽,其次,旋转会改变长方形的中心点位置。 求解方法: 计算旋…

    2025年12月24日
    000
  • 旋转长方形后如何计算其在画布上的轴距?

    旋转长方形后计算轴距 假设长方形的宽、高分别为 200 和 20,初始坐标为 (100, 100),我们将它旋转一个任意角度。根据旋转矩阵公式,旋转后的新坐标 (x’, y’) 可以通过以下公式计算: x’ = x * cos(θ) – y * sin(θ)y’ = x * …

    2025年12月24日
    000
  • 如何计算旋转后长方形在画布上的轴距?

    旋转后长方形与画布轴距计算 在给定的画布中,有一个长方形,在随机旋转一定角度后,如何计算其在画布上的轴距,即距离左上角的距离? 以下提供一种计算长方形相对于画布左上角的新轴距的方法: const x = 200; // 初始 x 坐标const y = 90; // 初始 y 坐标const w =…

    2025年12月24日
    200
  • CSS元素设置em和transition后,为何载入页面无放大效果?

    css元素设置em和transition后,为何载入无放大效果 很多开发者在设置了em和transition后,却发现元素载入页面时无放大效果。本文将解答这一问题。 原问题:在视频演示中,将元素设置如下,载入页面会有放大效果。然而,在个人尝试中,并未出现该效果。这是由于macos和windows系统…

    2025年12月24日
    200
  • 如何计算旋转后的长方形在画布上的 XY 轴距?

    旋转长方形后计算其画布xy轴距 在创建的画布上添加了一个长方形,并提供其宽、高和初始坐标。为了视觉化旋转效果,还提供了一些旋转特定角度后的图片。 问题是如何计算任意角度旋转后,这个长方形的xy轴距。这涉及到使用三角学来计算旋转后的坐标。 以下是一个 javascript 代码示例,用于计算旋转后长方…

    2025年12月24日
    000
  • TypeScript 中如何约束对象为 CSS 属性?

    typescript 中如何约束对象为 css 属性 想要约束一个对象为 css 属性,以便在调用函数时得到自动补全提示,可以采用以下方法: 使用 react 的 cssproperties 类型 对于 react 项目,可以使用 react 提供的 cssproperties 类型: 立即学习“前…

    2025年12月24日
    300
  • 如何在 TypeScript 中约束对象为 CSS 属性?

    如何在 typescript 中约束对象为 css 属性? 在 typescript 中,为特定目的而约束对象类型是很重要的。在本文中,我们将探究如何将对象约束为包含 css 属性。 考虑以下函数: function setattrstoelement(el: htmlelement, attr: …

    2025年12月24日
    000
  • 如何使用 TypeScript 约束对象以匹配 CSS 属性?

    如何约束 typescript 对象以匹配 css 属性? setattrstoelement 函数接收两个参数,其中第二个参数应为 css 属性。对于 react 项目,可以使用 cssproperties 类型: import { cssproperties } from “react”;fun…

    2025年12月24日
    000
  • 为什么使用 :global 修改 Antd 样式无效?

    :global 修改 antd 样式为何无效 本文旨在帮助您解决在组件内使用:global修改 antd 全局样式未生效的问题。 问题描述 您在组件内使用:global修改 antd 按钮样式,但没有生效。完整代码可参考 https://codesandbox.io/s/fk7jnl 。 解决方案 …

    2025年12月24日
    000
  • 为什么在 React 组件中无法获得 Tailwind CSS 语法提示?

    为什么在 React 组件中无法获得 Tailwind CSS 语法提示? 你在 VSCode 中编写 HTML 文件时,可以正常获取 Tailwind CSS 语法提示。但当你尝试在 React 组件中编写 Tailwind CSS 时,这些提示却消失不见了。这是什么原因造成的? 解决方案 要解决…

    2025年12月24日
    000
  • 如何在 VSCode 中为 React 组件启用 Tailwind CSS 提示?

    在 vscode 中为 react 组件启用 tailwind css 提示 如果你在使用 vscode 编写 react 组件时,发现 tailwind css 提示无法正常显示,这里有一个解决方法: 安装 tailwind css intellisense 插件 这是实现代码提示的关键,确保你已…

    2025年12月24日
    200
  • CSS 砌体 Catness

    css 就像技术中的其他东西一样 – 它总是在变化和发展。该领域正在进行的开发是 css 网格布局模块级别 3,也称为 css masonry 布局。 theo 制作了一段视频,介绍了它的开发方式以及苹果和谷歌就如何实施它进行的辩论。 所有这些让我很高兴尝试 css 砌体! webkit…

    好文分享 2025年12月24日
    000
  • 如何在 VS Code 中解决折叠代码复制问题?

    解决 VS Code 折叠代码复制问题 在 VS Code 中使用折叠功能可以帮助组织长代码,但使用复制功能时,可能会遇到只复制可见部分的问题。以下是如何解决此问题: 当代码被折叠时,可以使用以下简单操作复制整个折叠代码: 按下 Ctrl + C (Windows/Linux) 或 Cmd + C …

    2025年12月24日
    000
  • 如何相对定位使用 z-index 在小程序中将文字压在图片上?

    如何在小程序中不使用绝对定位压住上面的图片? 在小程序开发中,有时候需要将文字内容压在图片上,但是又不想使用绝对定位来实现。这种情况可以使用相对定位和 z-index 属性来解决。 问题示例: 小程序中的代码如下: 顶顶顶顶 .index{ width: 100%; height: 100vh;}.…

    2025年12月24日
    000
  • safari怎么打开html5_Safari浏览器直接输入html5链接自动渲染打开【打开】

    Safari中正确渲染HTML5内容需采用file://协议、禁用本地限制、启用HTTP服务器或更新版本并开启实验性功能。具体包括:一、用file:///绝对路径打开本地HTML文件;二、勾选高级设置中的“显示开发菜单”并禁用本地文件限制;三、用Python启动本地HTTP服务,通过http://l…

    2025年12月23日
    000
  • html标题如何_设置HTML页面标题与层级【层级】

    应区分title元素与h1–h6语义标题:title设于head中且唯一,用于浏览器标签和SEO;h1为页面唯一主标题;h2–h6须严格递进嵌套;需用开发者工具验证层级完整性;ARIA仅作布局受限时的语义补充。 如果您希望在HTML页面中正确设置页面标题并合理组织内容层级结构,则需要区分页面的标题(…

    2025年12月23日
    000
  • 如何调出html_在浏览器中调出HTML开发者工具【工具】

    可通过五种方式调出HTML开发者工具:一、快捷键(Win/Linux用Ctrl+Shift+I,macOS用Cmd+Option+I);二、右键“检查”元素;三、菜单栏“更多工具→开发者工具”;四、Chrome/Edge中输入chrome://inspect并启用实验功能;五、直接按F12键。 如果…

    2025年12月23日
    000
  • ides中怎么运行html_idea中运行html步骤【指南】

    首先确认项目中存在HTML文件,如index.html,并确保已添加基本HTML结构。接着通过File→Settings→Plugins安装前端开发相关插件并重启IDEA。然后右键HTML文件选择Open in Browser,在默认或指定浏览器中预览页面。若需本地服务器环境,可安装Node.js后…

    2025年12月23日
    000
  • py怎么运行html文件_python运行html文件方法【教程】

    使用Python运行HTML文件的方法有三种:一是通过内置http.server模块启动本地服务器,命令为python -m http.server 8000;二是编写Python脚本自动化启动服务器,便于重复使用;三是利用webbrowser模块直接在默认浏览器中打开HTML文件,无需启动服务器。…

    2025年12月23日
    000

发表回复

登录后才能评论
关注微信