Loading... > 大到项目名、模块名、包名、对外暴露的接口,小到类名、函数名、变量名、参数名,只要是做开发,我们就逃不过“起名字”这一关 # 一、坏味道一:不精准的命名 > 命名过于宽泛,不能精准描述,这是很多代码在命名上存在的严重问题,也是代码难以理解的根源所在。 ```cpp class Demo { public: void processChapter(long chapterId) { Chapter chapter = repository_.findByChapterId(chapterId); if (chapter == nullptr) { throw new IllegalArgumentException("Unknown chapter [" + chapterId + "]"); } chapter.setTranslationState(TranslationState.TRANSLATING); repository_.save(chapter); } } ``` 问题就出在函数名上。这个函数的名字叫 processChapter(处理章节),这个函数确实是在处理章节,但是,这个名字太过宽泛。**将章节的翻译状态改成翻译中**或者**将章节的翻译状态改成翻译完**都可以称为处理章节,那么处理章节就是一个过于宽泛的名字,没有错,但不精准。 ## 1、常见的错误 我们看看下面这些词是不是经常出现在你的代码里:data、info、flag、process、handle、build、maintain、manage、modify 等等。这些名字都属于典型的过于宽泛的名字,只要稍微仔细想想,类似的名字你一定还能想出不少来。 回到前面那段代码上,首先,命名要能够描述出这段代码在做的事情。这段代码在做的事情就是“将章节修改为翻译中”。那是不是它就应该叫 **changeChapterToTranslating** 呢?我们为什么要把翻译状态修改成翻译中,这一定是有原因的,也就是意图。 函数封装的意义在于我们不想知道那么多的细节。 **一个好的名字应该描述意图,而非细节。** 我们把翻译状态修改成翻译中,是因为我们在这里开启了一个翻译的过程。所以,这段函数应该命名 **startTranslation**。 # 二、坏味道二:用技术术语命名 > 这是一种不费脑子的命名方式,但是,这种命名却会带来很多问题,因为它是一种基于实现细节的命名方式。 ```cpp List<Book> bookList = service.getBooks(); ``` 这个 bookList 变量之所以叫 bookList,原因就是它声明的类型是 List。这种命名在代码中几乎是随处可见的,比如 xxxMap、xxxSet。 Bad: 我需要把这个变量的类型从 List 改成 Set。变量类型你一定会改,但变量名你会改吗?这还真不一定,一旦出现遗忘,就会出现一个奇特的现象,一个叫 bookList 的变量,它的类型是一个 Set。这样,一个新的混淆就此产生了。 ```cpp List<Book> books = service.getBooks(); ``` 也许你发现了,这个名字其实更简单,但从表意的程度上来说,它却是一个更有效的名字。 **事实上,在实际的代码中,技术名词的出现,往往就代表着它`缺少了一个应有的模型`。** ## 1、改进点:应该用业务语言写代码 > 无论是不精准的命名也好,技术名词也罢,归根结底,体现的是同一个问题:对业务理解不到位。 编写可维护的代码要使用业务语言。怎么才知道自己的命名是否用的是业务语言呢?一种简单的做法就是,把这个词讲给产品经理,看他知不知道是怎么回事。 改进函数名字的好办法:先写一句注释描述这个函数的用途,再把这句注释变成函数的名字 让每个人根据自己的理解来命名,确实就有可能出现千奇百怪的名字,所以,一个良好的团队实践是,建立团队的词汇表,让团队成员有信息可以参考。 ```cpp public: void approveChapter(long chapterId, long **userId**) { ... } ``` 这个函数的意图是,确认章节内容审核通过。这里有一个问题,chapterId 是审核章节的 ID,这个没问题,但 userId 是什么呢?了解了一下背景,我们才知道,之所以这里要有一个 userId,是因为这里需要记录一下审核人的信息,这个 userId 就是审核人的 userId。 通过业务的分析,我们会发现,这个 userId 并不是一个好的命名,因为它还需要更多的解释,更好的命名是 **reviewerUserId**,之所以起这个名字,因为这个用户在这个场景下扮演的角色是审核人(Reviewer)。 ```cpp public: void approveChapter(long chapterId, long **reviewerUserId**) { ... } ``` 这个坏味道也是一种不精准的命名,但它不是那种一眼可见的坏味道,**而是需要在业务层面上再进行讨论**,所以,它是一种更高级的坏味道。 # 三、重构中对函数进行改名 > 如果只是用于改名,我会将这个重构称做函数改名 ```cpp double circum(radius) { return 2 * Math.PI * radius; } ``` ## 1、简单做法 找出所有使用旧的函数声明的地方,将它们改为使用新的函数声明 解决方案1: ```cpp double circumference(radius) { return 2 * Math.PI * radius; } ``` ## 2、迁移式做法 通过提炼一个新函数的方式添加一层间接,并把旧函数的调用转发给新函数。 比如要重构一个新的对外发布的API,在提炼出新函数之后,你可以暂停重构,将原来的函数声明为“不推荐使用”(desprecated),然后登客户端也使用新函数的时候,再将后端旧的接口给删除 解决方案2: ```cpp double circum(radius) { return circumference(radius); } double circumference(radius) { return 2 * Math.PI * radius; } ``` **这种重构手法同样适用于我无权修改的代码。** # 四、重构中对变量进行改名 > 对于作用域超出一次函数调用的字段,则需要更用心命名。 ## 1、变量作用域只在一个函数 对其改名最为简单,`找到所有变量的引用,然后修改过来就可以了` ## 2、变量的出现不只一个函数 在代码库的很多地方都会出现,有些地方读取变量值,有些地方更新它的值。 对于这种情况,一般使用getter或者setter对其进行封装。 ```cpp string getTitle() { return title; } void setTitle(string newTitle) { title = newTitle; } ``` 如果这个变量title被广泛使用,以至于我觉得应该先做封装才敢做改名,那就有必要保持这个状态,将变量的疯转在函数后面。`先封装完getTitle和setTitle 再去改title的名字`。 ## 3、利用上下文简化命名 ```cpp class User { private: String userName; String userPassword; String userAvatarUrl; //... } ``` 我们没有在成员变量的命名中重复添加“user”这样一个前缀单词,而是直接命名为 name、password、avatarUrl。在使用这些属性时候,我们能借助对象这样一个上下文,表意也足够明确。 ```cpp User user = new User(); user.getName(); // 借助user对象这个上下文 ``` 同理,函数参数也可以借助函数这个上下文来简化命名 ```cpp public: void uploadUserAvatarImageToAliyun(String userAvatarImageUri); //利用上下文简化为: void uploadUserAvatarImageToAliyun(String imageUri); ``` ## 4、给常量进行改名 如果想修改的是一个常量,我可以复制这个常量,`这样既不需要进行封装`,又可以逐步完成改名。 ```cpp const string title = "C++"; const string bookTitle = title; ``` 常量取代魔法数字 ```cpp public: double CalculateCircularArea(double radius) { return (3.1415) * radius * radius; } // 常量替代魔法数字 public: static const Double PI = 3.1415; double CalculateCircularArea(double radius) { return PI * radius * radius; } ``` # 五、重构中给字段改名 ```cpp class Student { public: Student(Person person) : name_(person.name), country_(person.country) { } string getName(){ return name_; } string getCountry{ return country_; } string name_; string country_; }; const Student st = {"chen xuyuan", "shanghai"}; ``` 现在需要将name 字段改为firstName. ## 第一步:修改访问函数 ```cpp class Student { public: Student(Person person) : firstName_(person.name), country_(person.country) { } string getName(){ return **firstName_**; } string getCountry{ return country_; } string **firstName_**; string country_; }; const Student st = {{"chen xuyuan", "shanghai"}};//persoon = {name : "chen xuyuan", country: "shanghai"} ``` ## 第二步:在构造函数中使用firstName\_字段 ```cpp class Student { public: Student(Person person) : country_(person.country) { **firstName_ = person.firstName_.empty()? person.name : person.firstName_);** } string getName(){ return firstName_; } string getCountry{ return country_; } string firstName_; string country_; }; const Student st = {{"chen xuyuan", "shanghai"}}; ``` ## 第三步:当调用的地方改为**firstName字段之后就可以修改构造函数** ```cpp class Student { public: Student(Person person) : firstName_(person.firstName_), country_(person.country) { } string getName(){ return firstName_; } string getCountry{ return country_; } string firstName_; string country_; }; const Student st = {(**firstname** : "chen xuyuan", country: "shanghai"}}; ``` ## 第四步:给访问函数改名**getFirstName** ```cpp class Student { public: Student(Person person) : firstName_(person.firstName_), country_(person.country) { } string **getFirstName**(){ return firstName_; } string getCountry{ return country_; } string firstName_; string country_; }; const Student st = {(**firstname** : "chen xuyuan", country: "shanghai"}}; ``` 最后修改:2025 年 07 月 01 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏