对象命名的一点思考

结城 Java 4 次阅读 2269 字 发布于 3 天前 预计阅读时间: 10 分钟


引言

大多数团队都倾向于功能堆砌式开发,需求来了就加逻辑或函数,却很少有人愿意花时间在设计上,尤其是在对象命名花费时间。

看似快速实现需求,通常会对代码的可读性产生坏的影响,进而影响可维护性。

命名是一门学问,良好的命名能极大优化开发体验,并且对接入AI有很大的帮助。

不能出现变量A在函数B中调用,干了注释C所描述的工作。

合适的命名不但决定了当前代码的优雅程度,也会潜移默化地影响未来对代码进行修改、扩展和重构时的思维路径与决策过程。

一、避免er或or结尾

想象你走进一家餐厅,你会如何点餐?

是要一份宫保鸡丁,还是告诉厨师:“你先放油,然后大火爆炒,然后再加调料”?

如果你选择后者,那么对应到程序设计中,大概就是下面的写法:

class FoodMaker {
    public Dish Cook(List<Step> cookingSteps) {
        foreach(var step in cookingSteps) {
            ExecuteStep(step);
        }
        return new Dish();
    }
}

// 客户端代码
var maker = new FoodMaker();
var steps = new List<Step> {
    new Step("放油"), 
    new Step("大火爆炒"), 
    new Step("加调料")
};
var dish = maker.Cook(steps);

FoodMaker ,它只是一个“做饭的人”或“执行器”,缺乏更多的“主观能动性”。

这不仅增加了使用者在思维层面的负担,也显得对专业厨师的经验和技能不够尊重。

相比之下,更贴近现实的方式是直接告诉对方“我需要一份宫保鸡丁”,厨师会根据自身经验和对食材的理解来完成这道菜。

例如:

Chef {
    public Dish PrepareKungPaoChicken() {
        // 厨师知道如何准备这道菜
        return new KungPaoChicken();
    }
}

// 客户端代码
var chef = new Chef();
var dish = chef.PrepareKungPaoChicken();

这样,Chef 完整体现了专业性与自主性:

  • 角色名称直接让人联想到一个可自主决策、拥有专业技能的人,而非简单的“烹饪器”。
  • 客户端只需表达“想要什么菜品”,而不必详述所有步骤。
  • 厨师可以根据具体的食材和场景调整做菜细节,为业务带来更多灵活性。

值得注意的是,很多以 “-er” 或 “-or” 结尾的对象(如 Manager、Processor、Controller、Validator 等)常常会呈现出类似的“过程化”倾向:它们更多体现的是过程集合而非业务主体。

当我们把命名改为更能传达专业身份或业务角色的名词时,往往会看到对象的“自我意识”和“主观能动性”随之提升,从而让整个系统的抽象层次更高、可维护性更好。

抽象事物本身,一个事物做什么提前指定。

客户只负责点单,不负责做菜逻辑;厨师只负责在顾客点单后做菜,至于顾客怎么要求不必在意。

二、避免出现上帝类

“Service”“Helper”“Utility”等后缀,更容易出现“上帝类”,一个类包揽全文,十分可怕。

在 AI 辅助编程工具的普及下,命名的清晰度在大型项目中显得尤为重要。

模糊或过于抽象的名字不仅会增加团队成员、甚至让3个月后的你自己的认知负担,也会让 AI 在大量上下文中难以理解该对象的真实意图,甚至产生误导性补全。

错误示例:模糊类集合一切

RestaurantService,名称看似管理餐厅的一切,却混合了做饭、送餐和财务等完全不同的功能。

模糊的类名充当上帝角色,凡是和类相关全都走这个统一类,完全没有任何区分。

public class RestaurantService {
    // 名称看似管理餐厅的一切,却混合了做饭、送餐和财务等完全不同的功能

    // 烹饪某道菜
    public void cookDish(String dishName) {

    }

    // 将菜品送到指定地址
    public void deliverFood(String address) {

    }

    // 安排厨师、服务员等员工的工作排班
    public void scheduleStaff(String staffName, String shift) {

    }

    // 处理餐厅每天或每月的财务结算
    public void handleBilling() {

    }
}

问题:

  1. 过于宽泛、抽象的类名:RestaurantService 似乎负责所有与餐厅相关的功能,从做菜到送餐再到财务结算,远远超出了单一职责的范围。
  2. 对 AI 的误导:当 AI 工具在大规模代码库中搜索或补全时,见到“RestaurantService”可能以为这里面能找到任何与餐厅运营相关的逻辑,补全时也可能把更多不相干的功能一股脑塞进来,很容易导致上帝类。
  3. 难以维护与扩展:假如将来要更改配送方式或财务记账逻辑,你会发现所有功能都耦合在同一个类里,一次改动极有可能波及整个系统。

更合理的设计:按专业分工拆分类

要让代码更易读、更具可维护性,也让 AI 分析更准确,我们可以将“餐厅运营”按照现实场景拆分成多个专门对象,让每个类名更能“自证其职”:

// 专注做菜逻辑
public class Kitchen {
    public void cookDish(String dishName) {
        // 专业处理做菜流程
    }
}

// 专注外卖配送
public class Delivery {
    public void deliverFood(String address) {
        // 专业处理送餐流程
    }
}

// 专注人员排班
public class StaffScheduling {
    public void schedule(String staffName, String shift) {
        // 专业处理员工排班
    }
}

// 专注财务结算
public class Billing {
    public void settleAccounts() {
        // 专门处理账务结算
    }
}

好处:

  1. 职责明确:Kitchen 只做烹饪,Delivery 只管外卖,StaffScheduling 只负责排班,Billing 用于结算财务。每个对象都有清晰的专业领域。
  2. 易于理解:无论是人还是 AI,看到类名便能迅速推断其功能,降低误解。
  3. 便于扩展:将来需要为“配送”加入自动调度,或为“做菜”加入菜单推荐逻辑,也可以在对应的类中单独迭代,而不会影响其他模块。

三、自适应变化

现实生活中,若宫保鸡丁的必需食材(例如花生)突然缺货,真正的专业厨师会主动寻找替代食材,而不会要求顾客重新“下指令”或“换个点餐方式”。

同理,在软件中,一个设计得足够“智能”的对象,也应该能在外部条件或业务需求变化时,自行调整内部逻辑,而不影响调用者的使用方式。

示例:面相过程的烹饪

public class CookingProcess {
    public Dish cook(String dishName) {
        // 无论食材是否缺货,都执行固定步骤
        // ...
        return new Dish(dishName); // 返回做好的菜
    }
}

局限:

  1. 一旦食材缺货,需要调用方自己判断是否可做这道菜,或修改烹饪流程。
  2. 每次需求变化,都得改动 cook 方法或调用代码,无法实现真正的“自适应”。

改进示例:适配环境变化

厨师是专业人士,能适应环境变化自行选择需要变更的产品。

下面的 Chef 内部自行决定花生是否可用,如果缺货就用其他食材替代。这样,即使后续有更多类似变化(换新调料、临时供应商等),也能集中在 Chef 内部调整,无需改动客户端调用代码。

public class Chef {
    // 模拟花生库存状态,可来自数据库或配置文件
    private boolean peanutsInStock = false;

    public Dish prepareDish(String dishName) {
        if ("宫保鸡丁".equals(dishName)) {
            // 如果花生缺货,就用其他干果替代
            if (!peanutsInStock) {
                // ...在内部改用腰果或其他替代食材
            } 
            // ...否则正常使用花生
        }
        // 其余烹饪逻辑
        return new Dish(dishName); 
    }
}

好处:

  1. 内聚性强:所有判断和替代逻辑都放在 Chef 内部,外界无需关心具体做法。
  2. 客户端调用不变:无论花生库存状态如何变化,调用者依旧只需“一句命令”点餐,即可得到结果。
  3. 可扩展:将来若再遇到更多类似场景,只需修改 Chef,不会影响已有的调用代码。

调用示例:不影响客户端调用

public class Main {
    public static void main(String[] args) {
        Chef chef = new Chef();
        // 客户端只需告诉厨师要做“宫保鸡丁”
        Dish dish = chef.prepareDish("宫保鸡丁");
        // 即使花生缺货,Chef 会自动使用其他食材,无需调用方改动
    }
}

无论花生库存如何,客户端调用方式始终一致。

具体的操作事由交给底层的逻辑处理,调用方无需关心底层具体实现,我们要修改也只需要改底层方式。

不管是搞模式还是搞架构,最终的目的就是:把容易变化的东西挡在底层悄悄解决,把稳定安全的高清接口留给调用方。

给时光以生命,给岁月以文明
最后更新于 2026-06-15