戴老师的博客 - 软件设计
type: channel
link: https://www.tushu.info/category/%E8%BD%AF%E4%BB%B6%E8%AE%BE%E8%AE%A1/
description: 软件设计
items: 面向对象编程：这里我说了算！
items: 一文读懂：领域驱动设计DDD
items: 揭秘“山姆黄牛”背后的技术逻辑：用Java实现会员管理系统的防黄牛策略
items: 项目开发全流程实战：经验、挑战与应对策略
items: HTTP服务七层架构技术探讨
items: 有经验的Java开发者和架构师容易犯的10个错误
items: 探索Java代码重构：优化之道与实践经验
items: DDD介绍
items: 并发编程的 15 条建议(译.html)
items: 一堂如何提高代码质量的培训课

面向对象编程：这里我说了算！
type: item
date: 2026-07-07T08:37:37+00:00
description: 面向对象编程：这里我说了算！自 9 岁拥有第一台 Commodore 64 家用电脑起，我就开始编程了。然而，面对“如何写出好代码”这个问题，我仍然感觉自己有很多需要学习的地方。在探索如何提高编程水平的过程中，我学习了很多种语言，其中大多数是以面向对象（Object-Oriented, OO）为主的。然而，令人惊讶的是，在我读过的大多数书本、杂志和网络文章中，存在着大量糟糕透顶的代码，但它们却被当作面向对象的范例。在这些代码中，我看到被违反得最频繁的原则是 “命令，不要去询问”（Tell, Don't Ask） 原则。这个原则的核心在于：一个对象应该命令其它对象该做什么，而不是去查询其它对象的状态来决定自己要做什么。查询其它对象的状态来决定做什么，也被称作 “功能嫉妒”（Feature Envy）。在面向对象编程中，一个对象被定义为由“对象状态”和“操作这个状态的方法”组成。在《Holub on Patterns: Learning Design Patterns By Looking At Code》这本书里，Allen Holub 在第一章中有一节的标题是“为什么 getter 和 setter 方法有害”。他在 JavaWorld 上的一篇文章里也谈论了这个问题。对所有的面向对象程序员来说，这应该是一篇“必读”文章。我有一些程序员同事，他们在为一个对象声明了属性后，第一步就是添加 getter 和 setter 方法。JavaBean 规范对于这种文化的推广负有很大责任。人们认为这是一种能让你写出可复用的模块化组件的好方法，但这已是很多年前的事了，时过境迁。编写带有 getter 和 setter 方法的类会导致过程式代码。通过 getter 和 setter 来获取数据进行操作的逻辑最终会遍布整个应用，进而经常导致应用内的重复（这违反了另外一个原则：DRY——不要自我重复 (Don't Repeat Yourself.html)）。这会致使产生很难维护的代码，当你对一个类做任何修改时，都会在整个应用内造成连锁式的牵连。用这种方式来暴露数据还会妨碍你重构你的类，因为对这样的属性的任何修改都意味着会影响到访问了这个属性的其它类。违反“命令，不要去询问”原则的另外一个副作用是，你的探询最终会变成严重依赖状态信息并带有很多前提条件。这会让人很难理解你究竟询问的是什么。你很可能会最终违反的第三个原则是，最少知识（Least Knowledge） 原则，也叫作 得墨忒耳定律（Law Of Demeter）。这个定律可以总结为下面一句话：一个类应该只跟它的直接朋友通话，不要跟陌生人说话。在类里面加入 getter 方法，你的代码最终会写成这样：if (person.getAddress(.html).getCountry() == "Australia") {这违反了得墨忒耳定律，因为这个调用者跟 Person 过于亲密。它知道 Person 里有一个 Address，而 Address 里还有一个 country。它实际上应该写成这样：if (person.livesIn("Australia".html)) {这并不违反得墨忒耳定律，因为这个调用者只跟它的直接朋友 Person 通话，而且它不知道内部情况。从“命令，不要询问”的视角来看，这也是更好的做法，因为确定这个 person 是否居住在 Australia 的逻辑被隐藏到了 Person 里。如果我们改变 Person 里存储国家信息的方式，这将不会影响任何依赖这个信息的其它类。另外一个这样写代码的好处是，它把我们的意图显示得很清楚。这是我会在以后的时间里讨论的另外一个话题。为了避免违反“命令，不要去询问”原则，有几样事情你要记在心里：在 IDE 里敲击快捷生成键前，询问自己 ‘五个为什么’，问问自己为什么一上来就添加 getter 和 setter 方法。不要询问对象的状态。要做什么，告诉它们该怎么做。操作所属的对象拥有这些操作的数据。进一步阅读我的一个朋友是这个思想的大力倡导者，他用 East Oriented 方式精彩地解释了这个原则。《Tell Don't Ask and the effect on testing》，出自 Steve Freeman 和 Nat Pryce，他们也是《Growing Object-Oriented Software》、《Guided by Tests》两本书的作者。[英文原文：I give the orders around here! ]说明：本文原文发表于 2012 年左右，文中提到的部分观点（如 JavaBean 规范的影响）具有特定的时代背景，但核心设计原则（Tell, Don't Ask、得墨忒耳定律）在当今面向对象设计…

一文读懂：领域驱动设计DDD
type: item
date: 2026-07-07T08:37:03+00:00
description: 前言领域驱动设计（Domain-Driven Design，简称 DDD）近年来备受关注，尤其是随着 2B 业务和 SaaS 模式的兴起。然而，不少开发者在阅读 Eric Evans 的《领域驱动设计》或 Vaughn Vernon 的《实现领域驱动设计》后，常感到内容过于抽象，难以落地。本文将在介绍 DDD 基本概念的同时，通过一个虚拟的公司和项目场景，详细介绍领域驱动设计的落地实践。什么是领域我习惯将领域描述为财务领域、人力领域、房地产领域、金融领域等。在领域内，各种业务规则与业务知识盛行。如何有效把控规则的变化、应对复杂知识？有一个关键的四字词语：分而治之。分治法在很多场景下体现了其强大的作用力。领域本身很大很复杂，那就拆分，得到更小的领域，也即子域。如同递归调用一般，将一个复杂问题拆分单独求解，最终将解汇总得到复杂问题解。怎么拆？拆成什么样合适？依据什么拆？这些在领域驱动设计中都有了一套答案。虽然领域驱动设计不是银弹，但可以说是一套极好的系统方法论，或称为架构设计的方法论。领域驱动设计常以战略设计与战术设计来将整个领域展现得淋漓尽致，其作用范围既面向业务也面向技术。从战略角度的整体性（个人更喜欢称其为上帝视角）去规划系统、划分领域；而从战术角度则从技术层面来指导我们该如何去设计。为什么需要建立一个领域模型领域驱动设计告诉我们，在通过软件实现一个业务系统时，建立一个领域模型是非常重要和必要的，因为领域模型具有以下特点：抽象与边界：领域模型是对具有某个边界的领域的一个抽象，反映了领域内用户业务需求的本质；领域模型是有边界的，只反映了我们在领域内所关注的部分。业务无关技术：领域模型只反映业务，和任何技术实现无关；领域模型不仅能反映领域中的一些实体概念（如货物、书本、应聘记录、地址等），还能反映领域中的一些过程概念（如资金转账等）。逻辑集中：领域模型确保了我们的软件的业务逻辑都在一个模型中，都在一个地方；这样对提高软件的可维护性、业务可理解性以及可重用性方面都有很好的帮助。知识转化：领域模型能够帮助开发人员相对平滑地将领域知识转化为软件构造。全程贯穿：领域模型贯穿软件分析、设计，以及开发的整个过程；领域专家、设计人员、开发人员通过领域模型进行交流，彼此共享知识与信息；因为大家面向的都是同一个模型，所以可以防止需求走样，可以让软件设计开发人员做出来的软件真正满足需求。共同构建：要建立正确的领域模型并不简单，需要领域专家、设计、开发人员积极沟通共同努力，然后才能使大家对领域的认识不断深入，从而不断细化和完善领域模型。表达方式：为了让领域模型看得见，我们需要用一些方法来表示它；图是表达领域模型最常用的方式，但不是唯一的表达方式，代码或文字描述也能表达领域模型。核心价值：领域模型是整个软件的核心，是软件中最有价值和最具竞争力的部分；设计足够精良且符合业务需求的领域模型能够更快速地响应需求变化。领域通用语言（Ubiquitous Language）我们认识到由软件专家和领域专家通力合作开发出一个领域的模型是绝对需要的，但是，那种方法通常会由于一些基础交流的障碍而存在难点。开发人员满脑子都是类、方法、算法、模式、架构等等，总是想将实际生活中的概念和程序工件进行对应。他们希望看到要建立哪些对象类，要如何对对象类之间的关系建模。他们会习惯按照封装、继承、多态等面向对象编程中的概念去思考，会随时随地这样交谈，这对他们来说太正常不过了。但是领域专家通常对此一无所知，他们对软件类库、框架、持久化甚至数据库没有什么概念。他们只了解他们特有的领域专业技能。比如，在空中交通监控样例中，领域专家知道飞机、路线、海拔、经度、纬度，知道飞机偏离了正常路线，知道飞机的发射。他们用他们自己的术语讨论这些事情，有时这对于外行来说很难直接理解。如果一个人说了什么事情，其他的人不能理解，或者更糟的是错误理解成其他事情，又有什么机会来保证项目成功呢？在交流的过程中，需要做翻译才能让其他的人理解这些概念。开发人员可能会努力使用外行人的语言来解析一些设计模式，但这并不一定都能成功奏效。领域专家也可能会创建一种新的行话以努力表达他们的这些想法。在这个痛苦的交流过程中，这种类型的翻译并不能对知识的构建过程产生帮助。领域驱动设计的一个核心的原则是使用一种基于模型的语言。因为模型是软件满足领域的共同点，它很适合作为这种通用语言的构造基础。使用模型作为语言的核心骨架，要求团队在进行所有的交流时都使用一致的语言，在代码中也是这样。在共享知识和推敲模型时，团队会使用演讲、文字和图形。这里需要确保团队使用的语言在所有的交流形式中看上去都是一致的，这种语言被称为“通用语言（Ubiquitous Language）”。通用语言应该在建模过程中广泛尝试以推动软件专家和领域专家之间的沟通，从而发现要…

揭秘“山姆黄牛”背后的技术逻辑：用Java实现会员管理系统的防黄牛策略
type: item
date: 2026-07-07T08:36:59+00:00
description: 揭秘“山姆黄牛”背后的技术逻辑：用 Java 实现会员管理系统的防黄牛策略在浙江绍兴的山姆超市外，“黄牛”现象曾引发广泛关注。这些“黄牛”通过提供带入和结账服务，让未办理会员卡的消费者也能进入超市购物。这一行为不仅扰乱了市场秩序，也对山姆会员商店的会员管理系统提出了严峻挑战。今天，我们就来探讨一下，如何用 Java 实现一个更为健壮的会员管理系统，从技术层面有效防止“黄牛”现象的发生。一、问题背景与需求分析山姆会员商店的会员制度是其核心竞争力之一，原则上会员需要通过会员卡才能进入超市购物。然而，“黄牛”利用系统漏洞，通过多次带人进入和结账，从中牟利。为了打击这种行为，我们需要对会员管理系统进行升级，使其具备以下核心功能：会员身份验证（Member Validation）：确保只有合法会员才能进入超市。消费频率监控（Consumption Monitoring）：对会员的消费频率进行实时监控，及时发现异常消费行为。黑名单管理（Blacklist Management）：将确认的违规“黄牛”会员加入黑名单，禁止其再次进入超市。二、系统设计为了实现上述功能，我们可以设计一个基于 Java 的会员管理系统。系统主要包含以下三个核心模块：会员验证模块：负责验证会员身份的真实性。消费监控模块：负责监控会员的消费频率，识别异常模式。黑名单管理模块：负责黑名单的添加、查询和删除操作。三、系统实现1. 会员验证模块会员验证模块主要通过会员卡号和密码进行身份验证。为了演示方便，我们使用 Java 中的 HashMap 来存储会员信息，其中键为会员卡号，值为会员密码。在实际生产环境中，这些数据应从数据库中读取。import java.util.HashMap; import java.util.Map; public class MemberValidator { private Map<String, String> members; public MemberValidator() { members = new HashMap<>(); // 初始化会员信息，这里以硬编码为例，实际应用中应从数据库读取 members.put("123456", "password123".html); members.put("654321", "password321".html); } public boolean validateMember(String cardNumber, String password.html) { return members.containsKey(cardNumber.html) && members.get(cardNumber.html).equals(password.html); } }2. 消费监控模块消费监控模块主要通过记录会员的消费时间和次数，来监控会员的消费频率。我们使用 HashMap 存储会员的消费记录，键为会员卡号，值为消费时间列表。当单位时间内的消费次数超过阈值时，系统将判定为异常。import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class ConsumptionMonitor { private Map<String, List<Long>> consumptionRecords; private static final int THRESHOLD = 5; // 设定消费频率阈值，例如 5 次 private static final long INTERVAL = 3600 * 1000; // 设定时间间隔，例如 1 小时 public ConsumptionMonitor() { consumptionRecords = new HashMap<>(); } /** * 记录消费并检查是否异常 * @return 如果消费行为正常返回 true，否则返回 false */ public boolean recordConsumption(String cardNumber.html) { long currentTime = System.currentTimeMillis(); consumptionRecords.putIfAbsent(cardNumber, new ArrayList<>(.html)); List<Long> record = consumptionRecords.get(cardNumber.html); // 清理过时记录 record.removeIf(time -> currentTime…

项目开发全流程实战：经验、挑战与应对策略
type: item
date: 2026-07-07T08:36:51+00:00
description: 一、项目开发流程解析在当今数字化浪潮中，项目开发是一场充满挑战与机遇的征程。成功的项目交付不仅依赖精湛的技术，更离不开科学的管理和高效的协作。以下将深入探讨项目开发过程中的关键环节，分享实战经验与应对策略，助力项目团队提升交付质量与成功率。（一）签订合同：明确项目边界合同条款的关键意义在项目启动初期，合同如同项目的“宪法”，明确规定了项目的范围、功能以及各方的权利和义务。一份清晰、详细的合同是项目顺利推进的基石，能够有效避免后期的诸多纠纷和风险。案例警示：在某软件项目中，由于合同对功能范围界定模糊，导致开发过程中客户不断提出新需求，项目范围无序扩大（Scope Creep），而预算未相应增加，最终导致进度严重延误、成本大幅超支。应对策略与技巧明确边界：签订合同时，务必详细描述系统的边界和功能范围，避免使用模糊措辞。可采用列表、图表等直观方式，清晰呈现项目涵盖的具体功能模块及不包含的内容（Out of Scope）。变更管理：针对需求变更，制定明确的变更管理条款，规定不同级别变更的处理流程及费用调整机制。例如，小型变更在一定工作量范围内可由团队消化但需告知客户；重大变更则需重新评估计划、成本和时间，并获得客户书面确认。方案同步：建议将解决方案与合同一并签署，使客户对项目的实现方式有清晰预期，减少后期因理解差异导致的需求变更。（二）团队建设：打造高效协作团队项目经理的核心作用项目经理（Project Manager, PM）是项目团队的核心，其综合能力对项目成败起着至关重要的作用。优秀的 PM 不仅需具备扎实的专业知识和项目管理经验，更要善于沟通协调、激励团队成员，引领团队朝着目标前进。在项目立项后，应尽早确定项目经理，并赋予其足够的权力和资源，以便其能够有效地组织和管理项目团队。团队成员的选择与协作客户协同：尽力争取让客户的相关人员加入项目团队作为统一联系人，避免多头指挥导致的混乱。同时，确保客户方内部在与项目组沟通前达成一致意见，减少因客户内部矛盾给项目带来的干扰。优势互补：项目经理应根据项目特点和需求，精心挑选团队成员。例如在电商项目中，需配备熟悉业务流程的业务专家、擅长架构设计的技术高手、精通前后端开发的程序员以及严谨负责的测试人员，组成优势互补的团队。氛围营造：注重团队成员之间的沟通与协作，定期组织团队会议、技术交流活动等，促进信息共享和知识传递，营造良好的团队氛围。（三）需求调研：精准把握客户需求需求调研的重要性与常见问题需求调研是项目开发的源头，其质量直接影响项目的方向和成果。然而在实际项目中，常因对客户组织架构、业务流程和人员需求了解不足，导致需求不完整、不准确，后期频繁变更，给项目带来巨大风险。案例警示：某企业资源规划（ERP）项目，由于前期需求调研不深入，未充分考虑不同部门之间的业务协同需求，开发出的系统无法满足企业整体运营管理要求，不得不进行大规模返工，造成时间和成本的严重浪费。改进措施与方法干系人分析：全面了解项目干系人，绘制组织结构图，明确各方关系和职责，确保需求调研覆盖所有相关人员。与建设单位的业务管理部门和实际使用者进行深入交流，收集原始资料。流程文档化：详细描述业务流程，对现有系统进行深入调研，分析其优缺点和用户操作习惯。将业务流程文档化，与客户反复讨论、确认，确保准确性和完整性。可视化调研：采用可视化需求调研方法，根据不同客户群体提供相应的图表工具，如系统总体框架图、业务流程图、数据流图、用户界面图等，引导客户挖掘潜在需求。规格说明书：强调需求文档的重要性，《功能规格说明书》应具备完整性、正确性、可行性、必要性、优先级明确、无二义性、可验证性、一致性、可修改性和可跟踪性等特点，为项目验收提供有力依据。（四）架构设计：确保系统稳健性与可扩展性架构设计的挑战与风险架构设计是项目的技术核心，直接关系到系统的性能、可维护性和可扩展性。若缺乏充分的沟通和协作，容易导致设计偏离需求，后期改动困难，甚至影响项目进度和质量。案例警示：在某大型分布式系统开发中，由于架构师未与开发人员充分沟通，对系统性能需求估计不足，导致系统在高并发情况下频繁出现卡顿和崩溃，不得不重新优化架构，耗费大量时间和精力。全员设计理念与实践责任共担：全员设计理念强调将详细设计任务合理分解，让更多团队成员参与其中，共同承担设计责任，从而发挥专业优势，提高设计质量。任务分解：技术主管根据需求特点，将其分解为多个设计任务分配给合适的程序员。每个设计者在透彻理解需求的基础上，结合系统架构和产品现状，撰写详细设计文档。交叉审阅：设计文档完成后，通过交叉审阅机制，邀请其他程序员提出意见和建议。设计者汇总反馈并修正设计，经过多轮迭代直至文档达到最优状态，再交付开发。（五）沟通：项目成功的桥梁沟通在项目中的关键作用有效的沟通是确保团队协作顺畅、客户需求准确…

HTTP服务七层架构技术探讨
type: item
date: 2026-07-07T08:35:35+00:00
description: 编者注：本文为历史博文归档；涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效，阅读时请注意时效性。前言许久未进行技术分享，现将曾经在老新浪论坛中使用过的架构技术进行改进和整理，总结成文。欢迎交流指正。1. 为什么分层？计算机领域的体系结构普遍采用分层设计。从整体结构来看，从最底层的硬件往高层依次有：操作系统驱动程序运行库系统程序应用程序从网络分层模型 OSI 来讲，由上至下为：应用层表示层会话层传输层网络层数据链路层物理层当然，实际应用的 TCP/IP 协议分层并没有 OSI 标准这么复杂。从 C 语言文件编写到生成可执行文件的过程来看：预处理：展开后的 C 语言代码。编译：成汇编语言（特定 CPU 体系结构的汇编语言源文件）。汇编：生成目标文件（CPU 可执行的二进制指令机器码）。链接：连接目标文件生成可执行文件（操作系统可以加载执行的二进制文件）。这虽不算是软件的分层结构，但可以理解为一种通过分层来简化复杂问题的思想。那么 PHP 语言可以认为是建立在 C 语言之上的层——其解释器 Zend 引擎是用 C 语言实现。毕竟用 PHP 这样的脚本语言编写动态网页要比用 C 语言方便得多。当然，还有我们最熟悉的 MVC 分层技术，后面会做详细介绍。分层的好处想必大家都已经比较熟悉，这是一种“分而治之，大而化小”的思想。说到分层就不得不提模块，其实分层和模块是从两种维度来进行“分而治之”的方式：模块：是从横向维度将一个整体分割成若干个独立的部分，每个部分行使自己的职责，当然它们之间也可能有依赖关系，这通过其对外提供的服务来实现。如果将整个系统比做中国版图，那么模块方式将中国分为省、自治区等。分层：则是从纵向维度将一个整体从高至低划分为若干个独立的层，一个完整的服务由底层至上层，层层传递最终产出。分层和模块可以同时运用。例如中国用模块方式分为省之后，每个省的行政机构利用分层方式来行使职责，从低到高有户、村、乡、区、市、省等等，每一层都向上一层汇报。分层和模块会提高系统复杂度并影响效率（户不能直接向省汇报，而需要一级一级向上汇报），但是这样有利于系统的扩展和维护。每一层只需要关注自己提供的服务接口以及它下一层所提供的服务接口。试想一下，如果省需要接受来自市、区、乡、村等所有下级层的汇报，那些省干部会很头疼的。2. HTTP 服务传统的三层架构 MVCHTTP 服务中最经典的分层架构非 MVC 莫属了，几乎任何一个 PHP 开发框架都是支持 MVC 分层模式。此模式历史也比较悠久，是在上个世纪八十年代为编程语言 Smalltalk 设计的软件模式，至今已经被广泛引用。这里引用了百度百科中的图片：关于 MVC 的优点我不做介绍，搜索引擎中能找到大量相关资料。本文的标题是《HTTP 服务七层架构技术探讨》，比 MVC 多出了四层，这样复杂的分层是否有必要呢？关于这个问题可谓仁者见仁，智者见智。本人认为 MVC 分层粒度不够精细，当然你也可以继续坚持传统的三层，那么后文你也没必要看下去了。那么为什么 MVC 分层不够精细呢？在我曾经使用开源框架的 MVC 模式的经验中发现，V 和 C 层功能职责一般都很清晰稳定，但是 M 层却常常显得臃肿笨拙。C 层（Controller）：主要是负责整体流程控制。一般规范的架构中，流程都可以用一张或几张流程图画出，表明流程一般都是固定的。V 层（View）：主要是负责页面呈现。可能使用 Smarty 模板引擎，也可能是自带的模板引擎，显示的页面可能是 HTML、XML 或者 JSON。这些种类再多也都是可以度量的，所以 V 层也可以说是固定的。M 层（Model）：却关系到系统的业务逻辑。随着系统不断迭代更新，M 层中的内容也会不断演变。而这一层中也有很多复杂的处理，如文件读取、数据库查询、缓存策略、逻辑运算、数据加工、数据打包等等。所以 MVC 三层模型中，M 层是还能再做细分的。当 M 层有一个更精细合理的分层方式之后，我们的业务逻辑演变过程会更加的得心应手。3. 七层架构由上面的介绍，我们对 MVC 中的 M 层再进行分层规划。我这里给出的是一种对 M 层分五层的方式，读者如果觉得五层太多或者太少，可以参考这个再进行规划。原来的 M 层被分为：A 层：Application（应用层）B 层：Business（业务层）C 层：Component（组件层）D 层：Datadriver（数据驱动层）S 层：Systemdriver（系统驱动层）那么整个七层架构则为：ControllerViewApplicationBusinessComponentDatadriverSystemdriver结构图还是参考经典 MVC，将其中 M 层换成新的五层即可。现在依次介绍这几个新的层：3.1 …

有经验的Java开发者和架构师容易犯的10个错误
type: item
date: 2026-07-07T08:35:31+00:00
description: 有经验的 Java 开发者和架构师容易犯的 10 个错误即使是经验丰富的 Java 开发人员和架构师也会犯错。今天，我们讨论了一些如果您不密切注意，可能会犯的常见 Java 开发错误！我根据困扰程度将它们按降序排列——#1 最让我困扰，但根据其他因素，所有这 10 个错误都会给您带来同样的伤害 ;-)常见 Java 错误 #10：滥用或误解依赖注入依赖注入（Dependency Injection, DI）通常被认为是企业项目中的“最佳实践”。使用它似乎不会出错，但事实真的如此吗？DI 的基本思想之一是：您无需在对象内部查找其依赖关系，而是在以定义良好的方式（使用 DI 框架）创建对象之前初始化依赖关系。在实际创建对象时，只需将其传递给对象即可——可以通过构造函数中的预配置对象（构造函数注入）或使用 setter 方法（方法注入）。最重要的是，您传递的是对象所需的最终依赖，仅此而已。但是，即使在全新项目中，我仍然可以找到这样的代码：public class CustomerBill { // 由 DI 框架注入 private ServerContext serverContext; public CustomerBill(ServerContext serverContext.html) { this.serverContext = serverContext; } public void chargeCustomer(Customer customer.html) { CreditCardProcessor creditCardProcessor = serverContext.getServiceLocator().getCreditCardProcessor(); Discount discount = serverContext.getServiceLocator().getActiveDiscounts().findDiscountFor(customer.html); creditCardProcessor.bill(customer, discount.html); } }当然，这不是真正的 DI。该对象仍然自行进行依赖查找。存在链式调用代码（Train Wreck）serverContext.getServiceLocator().getCreditCardProcessor() 是这里出问题的另一种指示。正确的方法是像这样直接传递最终对象：public class CustomerBillCorrected { // 由 DI 框架注入 private ActiveDiscounts activeDiscounts; // 由 DI 框架注入 private CreditCardProcessor creditCardProcessor; public CustomerBillCorrected(ActiveDiscounts activeDiscounts, CreditCardProcessor creditCardProcessor.html) { this.activeDiscounts = activeDiscounts; this.creditCardProcessor = creditCardProcessor; } public void chargeCustomer(Customer customer.html) { Discount discount = activeDiscounts.findDiscountFor(customer.html); creditCardProcessor.bill(customer, discount.html); } }常见 Java 错误 #9：伪装 Java 更像 Perl与其他语言相比，Java 的优点之一是其类型安全方法。在您是唯一开发人员的小型项目中，几乎可以采用任何喜欢的编码样式。但是，在大型 Java 代码库和复杂的系统上，出现问题时需要早期警告。大多数编程错误应在编译时而不是在运行时捕获。Java 提供了许多便利来促进这些编译时警告。但是，当您像这样编写代码时，并没有什么能阻止您搬起石头砸自己的脚：public class AnimalFactory { public static Animal createAnimal(String type.html) { switch (type.html) { case "cat": return new Cat(); case "cow": return new Cow(); case "dog": return new Dog(); default: return new Cat(); } …

探索Java代码重构：优化之道与实践经验
type: item
date: 2026-07-07T08:34:29+00:00
description: 在软件开发领域，代码如同建筑的基石，其质量直接影响软件的可维护性、扩展性和性能。随着项目的不断演进，代码可能会逐渐变得复杂、冗余，甚至难以理解和维护。此时，代码重构（Refactoring）成为了提升代码质量、保持软件健康发展的关键手段。本文将深入探讨 Java 代码重构的相关知识，包括其定义、重要性、常见方法、重构的心理阶段以及实际案例，旨在帮助读者更好地理解和应用代码重构技术，提升 Java 编程能力。代码重构的定义与重要性定义代码重构是指在不改变代码外部行为的前提下，对代码内部结构进行优化和改进的过程。它旨在提高代码的可读性、可维护性和可扩展性，使代码更易于理解、修改和扩展。通过重构，我们可以消除代码中的重复、优化算法、改善代码结构，从而提升软件的整体质量。重要性提高代码质量良好的代码结构和清晰的逻辑有助于减少错误的发生，提高软件的稳定性和可靠性。例如，通过合理的变量命名、函数拆分和代码组织，可以使代码更易于阅读和理解，降低出错的概率。增强可维护性当代码需要修改或扩展时，清晰的结构和简洁的逻辑能够让开发人员更快地定位问题和进行修改。例如，遵循设计模式和单一职责原则（Single Responsibility Principle）的代码，每个类和函数只负责一项明确的任务，修改时不会影响到其他无关部分。促进团队协作易于理解的代码便于团队成员之间的协作和交流。大家可以更快地理解彼此的代码，减少沟通成本，提高开发效率。例如，在团队项目中，统一的代码风格和规范有助于成员之间的协作。Java 代码重构的常见方法使用接口和泛型优化代码结构原始代码问题分析在 Java 开发中，经常会遇到代码冗余的问题。例如，在操作缓存的代码中，获取 Session 和 ObjectMap 实例的代码可能在多个方法中重复出现，违反了 DRY（Don't Repeat Yourself）原则。这不仅增加了代码量，还使得代码难以维护，一旦需要修改获取实例的方式，就必须在多个地方进行修改。重构步骤与示例代码创建接口：创建一个带有抽象方法的 Executable 接口，用于传递包含业务逻辑的实例。例如：@FunctionalInterface interface Executable<T> { T execute(ObjectMap map.html) throws ObjectGridException; }封装重复代码：创建 executeWithMap() 方法，封装获取 Session 和 ObjectMap 实例以及执行异常处理的重复代码。该方法接受一个 Executable 接口的实例，执行其中的业务逻辑并返回结果。例如：private <T> T executeWithMap(Executable<T> ex.html) { Session s; try { s = oGrid.getSession(); ObjectMap map = s.getMap(cacheName.html); // 执行业务逻辑 return ex.execute(map.html); } catch (ObjectGridException oge.html) { throw new RuntimeException("Error performing cache operation", oge.html); } finally { if (s != null.html) s.close(); } }替换原始代码：使用 Lambda 表达式或匿名内部类实现 Executable 接口，替换原始方法中的重复代码。例如：public V get(final K key.html) { return executeWithMap((ObjectMap map.html) -> (V.html) map.get(key.html)); } public void put(final K key, final V value.html) { executeWithMap((ObjectMap map.html) -> { map.upsert(key, value.html); return null; }); } public Map<K, V> getAll(Set<? extends K> keys.html) { final List<K> keyList = new ArrayList<K>(); keyList.addAll(keys.html); List<V> valueList = executeWithMap((ObjectMap map.html) -> map.getAll(keyList.html)); Map<K, V> map = new Hash…

DDD介绍
type: item
date: 2026-07-07T08:33:20+00:00
description: DDD（领域驱动设计）全面介绍领域驱动设计（Domain-Driven Design，简称 DDD），是由埃里克·埃文斯（Eric Evans）在 2003 年出版的《领域驱动设计：软件核心复杂性应对之道》中提出的一种软件设计方法论。其核心思想是让软件设计与业务领域紧密对齐，通过聚焦业务核心逻辑、梳理领域概念，解决复杂业务系统的设计难题，避免代码与业务脱节及后期维护成本高昂的问题。一、DDD 的核心目标DDD 并非一套固定的“编码规范”，而是一种“设计思想”。其核心目标可概括为以下三点：降低复杂性：将复杂的业务系统拆解为更小、更易理解的“领域单元”。每个单元聚焦特定业务场景，避免整体系统陷入混沌。对齐业务与技术：让开发人员、产品经理、业务专家使用统一的“领域语言”沟通（避免技术侧说“接口”、业务侧说“流程”的认知偏差），确保软件实现真正满足业务需求。提升系统可维护性：业务逻辑集中在“领域层”，与技术框架（如数据库、API 层）解耦。后续业务变更时，只需修改领域层，无需大面积重构技术代码。二、DDD 的核心概念DDD 通过一系列抽象概念梳理业务逻辑，这些概念是理解 DDD 的基础，可分为“领域基础概念”和“设计模式概念”两类。1. 领域基础概念（1）领域（Domain）与子领域（Subdomain）领域：指软件要解决的“业务范围”，例如“电商领域”、“物流领域”、“金融支付领域”。子领域：将一个大领域拆解为更小的、聚焦单一业务方向的单元。例如“电商领域”可拆分为：核心子领域：订单管理（业务核心，直接决定业务价值）；支撑子领域：用户管理、商品管理（为核心服务，需自主开发）；通用子领域：日志、权限、通知（非业务专属，可复用现有组件或第三方服务）。（2）领域模型（Domain Model）领域模型是对业务领域中“概念、规则、流程”的抽象表达，不是数据库表的映射，而是业务逻辑的载体。例如“订单领域模型”会包含“订单状态流转规则（待支付→已支付→已发货）”、“订单金额计算逻辑（商品价 + 运费 - 优惠券）”等核心业务逻辑。（3）限界上下文（Bounded Context）DDD 中最关键的概念之一，指“一个边界，边界内的领域模型具有统一的语义（即领域语言），边界外的模型无需强关联”。作用：解决“同一概念在不同业务场景下含义不同”的问题。例如“用户”在“订单上下文”中只需“用户 ID、收货地址”，在“用户中心上下文”中则需要“手机号、密码、实名认证信息”——两个上下文的“用户模型”可独立设计，避免冗余。示例：电商系统的限界上下文可划分为“订单上下文”、“商品上下文”、“支付上下文”、“物流上下文”。2. 设计模式概念DDD 提供了具体的模式来落地领域模型，核心模式包括：概念定义与作用示例（电商订单场景）实体（Entity）具有唯一标识（如 ID）、状态可变化的业务对象，核心是“身份”而非属性。订单（Order）：有唯一订单 ID，状态会从“待支付”变为“已完成”。值对象（Value Object）无唯一标识、不可变（创建后属性不修改）的业务对象，核心是“属性组合的含义”。收货地址（Address）：由“省、市、街道、邮编”组成，无 ID；金额（Money）：由“数值 + 币种”组成，不可修改（如需改金额，需新建 Money 对象）。聚合（Aggregate）由“实体 + 值对象”组成的“业务单元”，确保单元内数据一致性，对外暴露唯一“聚合根”。订单聚合：聚合根是“订单（Entity）”，包含“订单项（Value Object，如商品 ID、数量）”、“收货地址（Value Object）”；外部只能通过“订单 ID"操作聚合内的对象，避免直接修改“订单项”导致数据不一致。聚合根（Aggregate Root）聚合的“入口”，是聚合内唯一允许被外部访问的实体，负责维护聚合内的业务规则。订单（Order）是聚合根，外部要修改订单项，必须通过“订单的修改订单项方法”（而非直接操作订单项），确保订单项数量不超过商品库存。领域服务（Domain Service）封装“跨实体/跨聚合”的业务逻辑，本身无状态，仅负责协调领域对象完成业务操作。订单支付服务（OrderPaymentService）：协调“订单（修改状态）”、“支付（创建支付记录）”、“库存（扣减库存）”三个聚合，完成“支付成功后更新订单状态”的逻辑。领域事件（Domain Event）记录领域中发生的“重要业务事件”，用于实现“跨上下文通信”或“异步业务流程”。订单支付成功事件（OrderPaidEvent）：订单支付完成后发布此事件，“物流上下文”监听事件后自动创建物流单，“积分上下文”监听事件后为用户增加积分。仓储（Repository）封装领域对象的“持久化逻辑”（如数据库 CRU…

并发编程的 15 条建议(译.html)
type: item
date: 2026-07-07T08:32:38+00:00
description: 并发编程的 15 条建议 (译.html)内核专家 Bryan Cantrill 和 Jeff Bonwick 在 2008 年 9 月的《ACM Queue》上发表了《Real-world Concurrency》一文，提出了 15 条并发编程的建议。以下是对该文建议的简单摘录，部分条目附带了陈硕先生的注释。Know your cold paths from your hot paths. 弄清楚代码里的热门执行路径和冷门执行路径。对冷门路径，用粗粒度的锁即可。对热门路径——也就是那些必须高度并发才能实现所期望的高吞吐量的代码，应该更加小心，加锁的策略必须简单明了且细粒度。Intuition is frequently wrong—be data intensive. 直觉常常是错的，要靠数据说话。【陈硕】比如线程切换到底有多大开销，普通 mutex 加锁到底有多大代价，系统调用的开销如何，gettimeofday() 在 x86-64 Linux 是不是真的系统调用等等，都要靠数据说话。Know when—and when not—to break up a lock. 知道什么时候把一个锁拆成多个，并知道什么时候不必这样做。除了把全局锁拆成多个锁，另外一种常用的避免线程争用 (contention.html) 的办法是减少加锁的范围。比方说从共享的数据结构里移除 (remove and delete.html) 元素，其实 delete 这一步可以放到锁外面。Be wary of readers/writer locks. 警惕读写锁。初学者常犯的一个错误是，见到某个数据结构频繁读而很少写，那么就把 mutex 替换为 rwlock。这不见得是正确的。【陈硕】这条深得我心，muduo thread lib 目前就没有提供读写锁的封装。另外，这一条也能鉴别 另一篇关于线程争用的文章 不靠谱。Consider per-CPU locking. 考虑用每个 CPU 用一个锁。Know when to broadcast—and when to signal. 知道什么时候用单个唤醒，什么时候用广播唤醒。notifyAll() 通常表示状态变更，而 notify() 通常表示资源变得可用。滥用 notifyAll() 会导致 惊群现象。【陈硕】muduo thread lib 的 ThreadPool 区分使用 notify() 和 notifyAll()，可作参考。Learn to debug postmortem. 学会验尸。【陈硕】在程序中只使用 Scoped locking 来加锁的话，很容易从 call stack 查出死锁。参考《多线程服务器的常用编程模型》第 6 节 线程间同步。Design your systems to be composable. 设计系统，使之能扩充。【陈硕】比方说，把对对象的修改操作都挪到同一个线程，这样就不必加锁。参考 muduo 的 EventLoop::runInLoop()。Don't use a semaphore where a mutex would suffice. 如果 Mutex 就能解决问题的话，不要使用信号量 semaphore。【陈硕】muduo thread lib 有意识地不提供信号量的封装。Consider memory retiring to implement per-chain hash-table locks. 考虑用内存“退休”法来实现哈希表的按桶加锁。Be aware of false sharing. 知道什么是伪共享。跟多 CPU 的 Cache 有关，值得了解。Consider using nonblocking synchronization routines to monitor contention. 考虑使用非阻塞的加锁来观察线程争用。When reacquiring locks, consider using generation counts to detect state change. 在重新加锁时，考虑使用版本号来检测状态变更。Use wait- and lock-free structures only if you absolutely must. 只在别无它法时才使用无锁数据结构。Prepare for the thrill of victory—and the agony of defeat. 准备接受成功的喜悦和失败的痛苦。更详细的解释请看原文。Bryan Cantrill 是 dtrace 的主要作者，Jeff Bonwick 是 ZFS 和 Slab allocator 的发明者。说明：本文源自 2008 年…

一堂如何提高代码质量的培训课
type: item
date: 2026-07-07T08:32:35+00:00
description: 一堂如何提高代码质量的培训课编者注：本文为历史博文归档；涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效，阅读时请注意时效性。今天这堂培训课讲什么呢？我既不讲 Spring，也不讲 Hibernate，更不讲 Ext，我不讲任何一个具体的技术。我们抛开任何具体的技术，来谈谈如何提高代码质量。如何提高代码质量，相信不仅是在座所有人苦恼的事情，也是所有软件项目苦恼的事情。如何提高代码质量呢？我认为我们首先要理解什么是高质量的代码。高质量代码的三要素我们评价高质量代码有三要素：可读性、可维护性、可变更性。我们的代码要一个都不能少地达到这三要素的要求，才能算高质量的代码。1. 可读性 (Readability.html)一提到可读性似乎有一些老生常谈的味道，但令人沮丧的是，虽然大家一而再、再而三地强调可读性，但我们的代码在可读性方面依然做得非常糟糕。由于工作的需要，我常常需要去阅读他人的代码，维护他人设计的模块。每当我看到大段大段、密密麻麻的代码，而且还没有任何的注释时，常常感慨不已，深深体会到了这项工作的重要。由于分工的需要，我们写的代码难免需要别人去阅读和维护。而对于许多程序员来说，他们很少去阅读和维护别人的代码。正因为如此，他们很少关注代码的可读性，也对如何提高代码的可读性缺乏切身体会。有时即使为代码编写了注释，也常常是注释语言晦涩难懂、形同天书，令阅读者反复斟酌依然不明其意。针对以上问题，我给大家以下建议：1) 避免大段代码如果你有阅读他人代码的经验，当你看到别人写的大段大段的代码，而且还不怎么带注释，你是怎样的感觉？是不是“嗡”地一声头大。各种各样的功能纠缠在一个方法中，各种变量来回调用，相信任何人都不会认为它是高质量的代码，但却频繁地出现在我们编写的程序里。如果现在你再回顾自己写过的代码，你会发现，稍微编写一个复杂的功能，几百行的代码就出去了。一些比较好的办法就是分段。将大段的代码经过整理，分为功能相对独立的一段又一段，并且在每段的前端编写一段注释。这样的编写，比前面那些杂乱无章的大段代码确实进步了不少，但它们在功能独立性、可复用性、可维护性方面依然不尽人意。从另一个比较专业的评价标准来说，它没有实现低耦合、高内聚。我给大家的建议是，将这些相对独立的段落另外封装成一个又一个的函数。许多大师在自己的经典书籍中，都鼓励我们在编写代码的过程中应当养成不断重构的习惯。我们在编写代码的过程中常常要编写一些复杂的功能，起初是写在一个类的一个函数中。随着功能的逐渐展开，我们开始对复杂功能进行归纳整理，整理出了一个又一个的独立功能。这些独立功能有它与其它功能相互交流的输入输出数据。当我们分析到此处时，我们会非常自然地要将这些功能从原函数中分离出来，形成一个又一个独立的函数，供原函数调用。在编写这些函数时，我们应当仔细思考一下，为它们取一个释义名称，并为它们编写注释（后面还将详细讨论这个问题）。另一个需要思考的问题是，这些函数应当放到什么地方。这些函数可能放在原类中，也可能放到其它相应职责的类中，其遵循的原则应当是"职责驱动设计"（后面也将详细描述）。下面是我编写的一个从 XML 文件中读取数据，将其生成工厂的一个类。这个类最主要的一段程序就是初始化工厂，该功能归纳起来就是三部分功能：用各种方式尝试读取文件、以 DOM 的方式解析 XML 数据流、生成工厂。而这些功能被我归纳整理后封装在一个不同的函数中，并且为其取了释义名称和编写了注释：/** * 初始化工厂。根据路径读取 XML 文件，将 XML 文件中的数据装载到工厂中 * @param path XML 的路径 */ public void initFactory(String path.html){ if(findOnlyOneFileByClassPath(path.html)){return;} if(findResourcesByUrl(path.html)){return;} if(findResourcesByFile(path.html)){return;} this.paths = new String[]{path}; } /** * 初始化工厂。根据路径列表依次读取 XML 文件，将 XML 文件中的数据装载到工厂中 * @param paths 路径列表 */ public void initFactory(String[] paths.html){ for(int i=0; i<paths.length; i++.html){ initFactory(paths[i].html); } this.paths = paths; } /** * 重新初始化工厂，初始化所需的参数，为上一次初始化工厂所用的参数。 */ public void …