对象命名怎么上手?从现实世界

之前的一篇文章提到过关于对象的命名,核心观点是:命名应反映业务角色与专业性,以提升代码的可读性、内聚性与可扩展性,最终提升可维护性。

而可维护性是软件的系统的核心。

但理解这个概念可能需要一些背景知识和时间,那么如何快速上手?本篇文章对这些概念进行简单的阐述,并通过一个案例说明命名的进步过程。

核心领域与非核心领域的命名差异

对象命名首先需要区分我们在命名什么,对象所处的位置是核心领域(Core Domain)还是非核心领域(Non-Core Domain)。

      • 核心领域是指承载业务本质的关键部分,直接反映系统的核心价值。例如,在学校管理系统中,"课程表(CourseSchedule)"是核心领域,负责协调课程、教师和教室资源;在电商系统中,"订单(Order)"则是核心领域,涵盖商品购买的生命周期。核心领域的命名应高度贴近业务术语,体现专业性与角色职责。
      • 非核心领域则是支持性的技术性模块,如日志记录(Logging)、数据访问(DataAccess)。这些部分的命名可以适当使用技术性后缀(如"Manager""Repository"),以突出其辅助角色。

为何区分? 因为核心领域的命名直接影响业务逻辑的清晰度和系统的可维护性,而非核心领域更注重技术实现的通用性。例如,"CourseSchedule"比"ScheduleManager"更能直观表达课程安排的业务角色,而"LogManager"则适合非核心的日志功能。如果对应到DDD,这两部分通常被划分为核心域与支持域。

为什么核心域需要更贴近业务?核心域是业务的核心竞争力所在,需要高度的定制化和精确性。因此,应该避免使用过于通用的命名,以免掩盖业务的独特性。

命名尽量贴近领域,贴近现实世界

开头我们首先区分了核心领域与非核心领域,下面我们说的规则主要应用与核心领域。

对于核心领域,命名需要更多从现实世界中获取,这是因为现实世界的概念已经过长期发展和演化: 现实世界中的角色、领域概念,例如"顾客"、"订单"、"银行账户"、"医生"、"病人"等等, 并非凭空捏造,而是经过漫长的社会实践、商业活动、科学研究等发展而来。它们在实践中不断被验证、修正和完善,其边界、责任和功能通常已经非常清晰和成熟。

同时,现实世界中的对象由于较为常见,日常生活中已经理解这些对象的概念与意义,不再需要在软件中增加额外的负载(还记得上学时做英语阅读理解,如果主题和日常相关,很多单词不认识也能大概猜出文章意思,如果文章太抽象,提到的内容不在日常生活内,即使每个单词都认识,还是难以理解文章概念?)

下面是一个简单Demo说明,一个学校排课系统,通过合理的命名,能够延伸出显性和隐性的职责。

/**
 * 代表学校教学安排的领域对象
 * 核心职责是管理课程安排与资源协调
 */
public class CourseSchedule {
    // 核心职责(显性)
    private Map<TimeSlot, Classroom> assignedRooms;

    public void assignTeacher(Course course, Teacher teacher) {
        /* 分配授课教师并验证资质 */
    }

    // 核心职责(显性)
    public boolean checkTimeConflict(LocalDateTime newSlot) {
        /* 检查新时段是否与现有课程冲突 */
    }

    // 不应有的职责:
    // 1. 不处理学生考勤(属于AttendanceSystem)
    // 2. 不计算教师薪资(属于PayrollSystem)
    // 3. 不管理教学设备(属于TeachingEquipmentResource)

    // 隐含职责(后续扩展)
    public void generateIcalFeed() {
        /* 
         * 新增需求:生成日历订阅链接
         * 虽未在类名中体现,但现实中课程表天然需要时间同步功能
         * 添加此方法符合对象职责的自然延伸
         */
    }
}

不熟悉领域

上面的CourseSchedule表类虽然日常生活中容易理解,但也有一定的领域知识,如果我们一开始对领域并不熟悉,该怎么办?例如,"CourseSchedule"涉及学校管理的专业知识,作为开发者一开始比较懵逼,该怎么办?

从现实角色入手

通常来说,开发某个领域的代码,都可以先简单了解该领域的基础知识与词汇,当然有条件最好能与这个行业有经验的人沟通,也就是所谓的领域专家,比如CourseSchedule这个例子,可以提取名词--关键实体(如"课程""教室")和动词(如"安排""冲突检查"),逐步构建命名。

避免缺乏灵魂的技术后缀

如果一开始想不好名字,那么可以先尽可能避免使用"er""or"等模糊后缀,如"Scheduler""Processor",这些命名缺乏领域语义,难以反映业务角色,这可能导致丢失业务的精确度与定制性。但是本身已成为领域实体的词汇除外,例如"Teacher"。

渐进式演化

最开始如果并没有想到比较好的名字,可以从简单开始随着开发的进行,学习到新的领域,逐步演进,依然是CourseSchedule这个例子:

第1阶段 - ClassData(纯数据容器)

在这一阶段,命名仅关注数据的存储,缺乏业务语义的表达。ClassData作为一个纯数据容器,虽然简单易懂,但其局限性在于没有体现课程安排的业务角色,仅停留在技术层面的数据收集,职责边界模糊,无法反映现实世界的逻辑。

/**
 * 简单的课程数据类
 */
public class ClassData {
    private List<String> classes;
    private List<String> teachers;
    private List<String> rooms;
    
    public void addClass(String className, String teacher, String room) {
        /* 简单添加数据 */
    }
}

第2阶段 - ClassSchedule(引入业务概念)

相比ClassData,ClassSchedule迈出了关键一步,开始引入业务概念,命名从单纯的数据容器转向课程安排的初步表达。进步在于通过teacherAssignments和roomAssignments映射了教师和教室的分配关系,增加了简单的业务逻辑(如时间可用性检查)。然而,其局限性依然明显:命名仍偏技术化,未能完全贴近现实世界的"课程表"概念,职责定义较为粗浅,缺乏对领域规则的深入体现。

/**
 * 课程安排类
 */
public class ClassSchedule {
    private Map<String, String> teacherAssignments;  // 教师分配
    private Map<String, String> roomAssignments;     // 教室分配
    
    public void assignClass(String time, String teacher, String room) {
        /* 基础的课程安排逻辑 */
    }
    
    public boolean isTimeAvailable(String time) {
        /* 简单的时间检查 */
    }
}

第3阶段 -CourseScheduleManager(领域职责初具雏形)

相比ClassSchedule,CourseScheduleManager在业务表达上更进一步,通过引入Course和Teacher等领域实体,命名开始贴近学校管理的现实逻辑。进步体现在职责的细化,如教师分配加入了业务规则,时间冲突检查也更具体,体现了领域知识的深化。然而,其局限性在于"Manager"后缀仍带有技术化色彩,未能完全摆脱抽象管理的意味,可能模糊了"课程表"作为核心业务角色的直观性,限制了命名的语义精度。

/**
 * 课程表管理类
 */
public class CourseScheduleManager {
    private Map<TimeSlot, Classroom> roomAssignments;
    private Map<Course, Teacher> teacherAssignments;
    
    public void assignTeacher(Course course, Teacher teacher) {
        /* 增加了教师分配的业务规则 */
    }
    
    public boolean checkScheduleConflict(TimeSlot slot) {
        /* 增加了时间冲突检查 */
    }
}

最终阶段 - CourseSchedule(完整领域对象)

相比CourseScheduleManager,CourseSchedule完成了从技术视角到领域视角的转型,彻底贴近现实世界的"课程表"概念。进步在于去除了"Manager"后缀,直接以业务角色命名,职责边界更清晰,方法如generateIcalFeed自然延伸了课程表的时间属性,展现出可扩展性。此时已无明显局限性,命名与业务本质高度契合,直观&专业。

/**
 * 代表学校教学安排的领域对象
 * 核心职责是管理课程安排与资源协调
 */
public class CourseSchedule {
    private Map<TimeSlot, Classroom> assignedRooms;
    
    public void assignTeacher(Course course, Teacher teacher) {
        /* 分配授课教师并验证资质 */
    }
    
    public boolean checkTimeConflict(LocalDateTime newSlot) {
        /* 检查新时段是否与现有课程冲突 */
    }
    
    public void generateIcalFeed() {
        /* 生成日历订阅链接 */
    }
}

小结

命名是软件设计中看似微小却至关重要的环节,命名不仅仅是简单的代码问题,还是我们对业务本质理解水平的呈现,更是业务逻辑与领域专家沟通的关键。通过区分核心领域与非核心领域,我们明确了命名应优先服务于业务本质,尤其在核心领域中,贴近现实世界的概念能带来语义清晰、职责明确和可维护性提升。