作者:陈立(勤仁)
你可不能像给狗狗取名字那样给类、办法、变量命名。仅仅因为它很可恶或者听下来不错。
在写代码的时候,你要常常想着,那个最终保护你代码的人可能将是一个有暴力偏向的疯子,并且他还晓得你住在哪里。
01 为什么命名很重要?
在我的项目中,从我的项目的创立到办法的实现,每一步都以命名为终点,咱们须要给变量、办法、参数、类命名,这些名字呈现在代码的每个角落,随处可见,凌乱或谬误的命名不仅让咱们对代码难以了解,更蹩脚的是,会误导咱们的思维,导致对代码的了解齐全谬误。如果整个我的项目始终贯通着好的命名,就能给阅读者一个神清气爽的开始,也能给阅读者一个好的指引。
要晓得,代码的浏览次数远远多于编写的次数。请确保你所取的名字更侧重于浏览不便而不是编写不便。
02 为什么很难正确命名?
有人称编程中最难的事件就是命名。我同样深认为然,中国有句古话叫做万事开头难。抛开环境搭建,真正到了编码阶段第一件事就是命名,而最常见的一种状况,就是毫无目标、仅凭集体的爱好的去决定了一个名字。但因为没有想分明指标和具体实施步骤,所以进行过程中往往会面临无数次的小重构甚至是推倒重来。
1、不足志愿
胆怯在抉择名字上花工夫,对做好命名的志愿有余,得心应手,甚至忽视团队对命名的根本标准,感觉编译器能编译通过,代码能失常运行就成。
其实对发现的命名问题进行重构和推倒重来并不可怕,最可怕的是当下程序员不具备发现问题后肯回过头来纠偏的志愿。这终将演变成为一场劫难。
2、不足思考
没想分明被命名的事物是什么,事物应该承当什么职责,是否会对其他人造成误会。
老手程序员总会花很多工夫学习一门编程语言、代码语法、技术和工具。他们感觉如果把握了这些货色,就能成为一个好程序员。然而事实并不是这样,事实上,编程不仅仅关乎把握技能和工具,更重要的是在特定领域内解决问题的能力,还有和其余程序员单干的能力。因而,能在代码中精确的表白本人的想法就变得异样重要,代码中最直观的表达方式是命名,其次是正文。
3、不足技巧
选一个好的名字真很难,你可能得有较高的形容能力和独特的文化背景。并且通晓一些常见且应该防止的命名问题。
如果最终还是没法找到适合的名字,还请增加精确的正文辅助别人了解,等想到适合的名字后再进行替换,不过往往可能通过正文(母语)形容分明的事物,命名应该问题不大,问题大的是连正文都无奈精确表白,那阐明可能以后类、函数、变量承当的职责太多太杂。
03 如何正确的命名?
这里不探讨具体语言的命名规定,起因是不同编程语言命名规定各不相同,甚至不同团队间雷同语言的命名规定也有出入。这里次要从进步可读性登程,联合我所在的客户端团队日常开发状况,以 Java 作为演示语言,给一些对于命名的倡议。
1、货真价实
无论是变量、办法、或者类,在看到他名称的时候应该以及回答了所有的大问题,它应该通知你,它为什么会存在,他做什么事,应该怎么做。如果在看到名称时,还须要去查找正文来确认本人的了解,那就不算货真价实。而且在发现有更好的命名时,记得果决替换。
Case1:到底怎么算 End?
代码示例:
public interface OnRequestListener {
/**
* 申请完结 只有胜利点才认为是真正的完结
* @param ...
*/
void onRequestEnd(....);
/**
* 申请开始
* @param ...
*/
void onRequestStart(...);
}
大脑流动:
onRequestEnd 是申请的什么阶段?申请胜利和失败任一状况都算“end”吗?喔,原来正文有写:“只有胜利点才认为是真正的完结”。
批改倡议:
// 省略正文
public interface OnRequestListener {void onStart(....);
void onSuccess(....);
void onFailure(...);
}
2、防止误导
在每种语言中都有内置的标识符, 他们都有特定的含意,如果和他们没有关联就不要在命名中加上他们。
2.1 防止应用令人误会的名字
Case1:命错名的汇合
代码示例:
private List<SectionModel> dataSet;
大脑流动:
“dataSet”在最后肯定是为了元素去重抉择了 Set 类型,必定起初某一个历史时刻发现有 bug 被偷偷改成了 List 类型,然而变量名没变。
代码跟读:
跟踪提交记录,呃,在 18 年被刚定义时就是 List<*> dataSet;
批改倡议:
private List<SectionModel> dataList;
或者
private List<SectionModel> sections;
Case2:不是 View 的 View 类
代码示例:
/** 作者 + 日期 */
public class RItemOverlayView {}
/** 作者 + 日期 */
public class NRItemOverlayView {}
大脑流动:
“N”是啥意思?类名只有一个 N 的字母差异,难道是新旧的差异,新的和旧的有什么区别呢?
类名以 View 结尾,嗯,应该是一个视图,可是,视图为啥不必继承视图基类的?
代码跟读:
喔,N 的确代表“New”的意思,NRItemOverlayView 被首页举荐应用,RItemOverlayView 被购后举荐应用。
这个类次要外围工作是构建浮层视图(职责并不繁多),而类自身并不是一个真正的视图;
批改倡议:
// 放在首页举荐场景的包下
public class ItemOverlayViewCreator {}
// 放在购后举荐场景的包下
public class ItemOverlayViewCreator {}
Case3:整形变量为啥要用 is 结尾
代码示例:
private int isFirstEnter = 0;
大脑流动:
为什么“is”结尾的变量却申明成整形?到底是要计数还是判断虚实呢?
代码跟读:
isFirstEnter < 1 做第一次进入的逻辑
批改倡议:
private boolean isFirstEnter = true;
Case4:开关作用反掉啦
代码示例:
....
if (InfoFlowOrangeConfig.getBooleanValue(POST_DELAYED_HIDE_COVER_VIEW_ENABLE, true)) {hideCoverImageView();
} else {delayedHideCoverImageView();
}
大脑流动:
为什么开关名为“delay….”为“true”的时候,走的不是 delay 逻辑,那开关要怎么发?容我多看几遍,是不是最近没劳动好所以看岔了。
代码跟读:
重复看了几遍,的确是开关命名和实际操作齐全相同,开关名意为“提早暗藏封面视图”,执行的却是“立刻暗藏封面视图”。
批改倡议:
....
if (InfoFlowOrangeConfig.getBooleanValue(IMMEDIATELY_HIDE_COVER_VIEW_ENABLE, true)) {hideCoverImageView();
} else {delayedHideCoverImageView();
}
3、做有意义的辨别
如果单纯只是为了辨别两个名称不能一样,就应用就应用诸如数字,字母来做辨别的话,那仿佛是毫无意义的辨别。
3.1 防止在名字中应用数字
case1: 来自包名的暴击
问题示例:
以下是首页客户端的工程目录节选,数字化的包名:recommend、recommend2、recommend3、recommend4
大脑流动:
2、3、4 难道是因为首页历史包袱太沉重,举荐迭代的版本切实太多导致 Old、New 单词不够用所以用数字来代替新旧 4 个历史阶段的版本吗?
代码跟读:
- recommend:举荐的公共工具和模块;
- recommend2:收藏夹场景的举荐实现;
- recommend3:首页场景的举荐实现;
- recommend4:购后场景的举荐实现;
批改倡议:
这里临时只探讨如何把数字替换成有意义的命名
3.2 防止应用具备类似含意的名字
case1:同一个类下的“刷新 7 剑客”
代码示例:
大脑流动:
为什么一个 Adapter 类对外有七个刷新数据的接口?
“refreshData()” 和“speedRefreshData()”是什么区别?“mainRefreshData()”+ “refreshDeltaData()” =“mainRefreshDeltaData()”?
是一个拆分组合的关系吗?我应该在何总场景下如何正确的应用 refresh,我在哪,我在做什么?
代码跟读:
大部分 refresh 代码线上并不会被调用。浏览和调试下来,理论还在失效的办法只有一个:“gatewayRefreshData()”。
批改倡议:实际上这曾经不是一个单纯优化命名能够解决的问题,无论叫的多具体,面对 7 个刷新接口都会懵圈。冀望在办法申明期间,作者多体量起初的阅读者和维护者,及时的调整代码。
后来者能够从理论登程去假存真,做减法干掉其它无用的 6 个刷新办法保留一个刷新接口。
case2:4 个数据源定义,该用谁呢
代码示例:
申明 1:
public interface IR4UDataSource {....}
申明 2:
public interface RecommendIDataSource {....}
申明 3:
public interface IRecommendDataResource {....}
申明 4:
public class RecmdDataSource {....}
大脑流动:
4 个举荐数据源,其中有 3 个是接口申明,为什么接口定义了不能多态,不能复用接口的申明?这三代的形象如同有一丢丢失败。
代码跟读:
homepage 包下的 IR4UDataSource,和十分古老的首页已经爱过,线上理论不会应用;
Recommend2 包下的“RecommendIDataSource”属于收藏夹,但也属于古老版本,收藏夹不在应用;
Recommend3 包下的“IRecommendDataResource”的确是首页场景举荐应用,但也是已经的旧爱;
原来当今的真命天子是 Recommend3 包下的“RecmdDataSource”,一个应用俏皮缩写未继承接口的实体类,看来是曾经放弃假装。
批改倡议:
……
3.3 防止应用具备不同含意但却有类似名字的变量
case1 : 大家都是 view,到底谁是谁
代码示例:
public void showOverlay(@NonNull View view ...) {
...
View rootView = getRootView(view);
DxOverlayViewWidget dView = createDxOverlayViewWidget();
dView.showOverLayer(view.getContext(), (ViewGroup)rootView, cardData, itemData);
...
}
代码跟读:
代码中存在 3 个以 view 结尾的局部变量,rootView、view、dView,其中 view 和 dView 之间只有一个字母的差别,办法如果长一点,view 和 dView 应用频率在高一点,掺杂着 rootView 会让人抓狂。另外 dView 也并不是一个 view,理论是个 DXViewWidget。
批改倡议:
public void showOverlay(@NonNull View hostView ...) {
...
ViewGroup parentView = getParentView(hostView);
DxOverlayViewWidget dxOverlayViewWidget = createDxOverlayViewWidget();
dxOverlayViewWidget.showOverLayer(hostView.getContext(), parentView, ...);
...
}
4. 应用读的进去的名称
应用读的进去的名称,而不是自造词,这会给你无论是记忆,还是探讨须要阐明是哪个办法时,都能带来便当。能够应用达成共识的缩写,防止造成阅读障碍。
4.1 防止应用令人费解的缩写
Case1:接口定义中的俏皮缩写
代码示例:
/**
* Created by *** on 16/8/6.
*/
public interface IR4UDataSource {....}
大脑流动:
R4U 是什么?R4 和 Recommend4 这个目录有什么关系,难道是购后举荐的数据源定义吗?那 U 又代表什么?
代码跟读:
原来 R4U 是 Recommend For You 的俏皮写法
批改倡议:
public interface IRecommendForYouDataSource {....}
Case2:成员变量命名的缩写
代码示例:
....
// 题目指示器(indicators)private LinearLayout mTabLL;
private TabLayout mTabLayout;
....
大脑流动:
“mTabLL”是什么呢?有正文!难道 mTabLL 是指示器视图?“LL“”也不像是 indicators 的缩写,喔,LL 是 LinearLayout 的首字母缩写。嗯,应用 LinearLayout 自定义做成指示器有点厉害!诶,不对,如同 TabLayout 更像是个选项卡式指示器的样子。
代码跟读:
原来“mTabLL”上面申明的“mTabLayout”才是指示器视图,“mTabLL”只是指示器视图的父视图。还好“mTabLayout”没有缩写成“mTabL”,导致和“mTabLL”傻傻分不清,作者未然是手下留情了。
批改倡议:
....
private LinearLayout mTabLayoutParentView;
private TabLayout mTabLayout;
....
Case3:局部变量命名的缩写
代码示例:
....
for (PageParams.GroupBuckets ss:params.groupBucketIds.values()) {if (ss != null) {bucketIds.removeAll(ss.bucketIdsAll);
Collections.addAll(bucketIds, ss.currentBucketIds);
}
}
....
大脑流动:
“ss” 是什么鬼,是不是写错了,GroupBuckets 首字母缩写是“gb”,PageParams 和 GroupBuckets 的首字母缩写是“pg”
这难道是,PageParams 和 GroupBuckets 的尾字母缩写,在一个圈复杂度为 18 的办法中看到尾字母缩写“ss”?啊!好好受。
批改倡议:
for (PageParams.GroupBuckets groupBuckets :params.groupBucketIds.values()) {if (groupBuckets != null) {....}
}
5、应用可搜寻的名称
若变量或常量可能在代码中多处应用,则应赋其以便于搜寻的名称。
5.1 给魔法值赐名
Case1:数字魔法值没法搜寻也看不懂
代码示例:
public static void updateImmersiveStatusBar(Context context) {
....
if (TextUtils.equals(isFestivalOn, "1")) {if (TextUtils.equals(navStyle, "0") || TextUtils.equals(navStyle, "1")) {....} else if (TextUtils.equals(navStyle, "2")) {....}
}
....
}
大脑流动:
对于 TextUtils.equals(isFestivalOn, “1”),我还能猜想一下这里的“1”代表开关为开的意思。
那 TextUtils.equals(navStyle, “0”/”1″/”2″) 中的“0”,“1”,“2”我该如何晓得代表什么意思?
老板,请不要再问我为什么需要吞吐率不高,做需要慢了,可能是因为我的想象力不够。
批改倡议:
实际上,协定约定时就不应该以“0”,“1”,“2”这类无意义的数字做辨别申明。
public static final String FESTIVAL_ON = "1";
public static final String NAV_STYLE_FESTIVAL = "0";
public static final String NAV_STYLE_SKIN = "1";
public static final String NAV_STYLE_DARK = "2";
public static void updateImmersiveStatusBar(Context context) {
....
if (TextUtils.equals(isFestivalOn, FESTIVAL_ON)) {if (TextUtils.equals(navStyle, NAV_STYLE_FESTIVAL)
|| TextUtils.equals(navStyle, NAV_STYLE_SKIN)) {....} else if (TextUtils.equals(navStyle, NAV_STYLE_DARK)) {....}
}
....
}
5.2 防止在名字中拼错单词
Case1:接口拼错单词,实现类也被迫放弃队形
代码示例:
public interface xxx {
....
void destory();}
批改倡议:
public interface xxx {
....
void destroy();}
6、类的命名
应该总是名词在最初面,名词决定了这个类代表什么,后面的局部都是用于润饰这个名词;比方,如果当初你有一个服务,而后又是一 个对于订单的服务,那就能够命名为 OrderService,这样命名就是通知咱们这是一个服务,而后是一个订单服务;再比方 CancelOrderCommand,看到这个咱们就晓得这是一个 Command,即命令,而后是什么命令呢?就是一个勾销订单的命令,CancelOrder 示意勾销订单。
类的命名能够参考后面讲述过的规定。实际上往往理解一个类更多须要通过查看类的办法定义,而仅仅通过类名无奈通晓类是如何工作的。对于类的更多内容,会在后续章节具体开展。
7、办法的命名
能够用一个较强的动词带指标的模式。一个办法往往是对某一指标进行操作,名字应该反映出这个操作过程是干什么的,而对某一指标进行操作则意味着咱们应该应用动宾词组。比方:addOrder()。当办法有返回值的时候,办法应该用它所返回的值命名,比方 currentPenColor()。
《代码大全》:变量名称的最佳长度是 9 到 15 个字母,办法往往比变量要简单,因此其名字也要长些。有学者认为失当的长度是 20 到 35 个字母。然而,一般来说 15 到 20 个字母可能更事实一些,不过有些名称可能有时要比它长。
7.1 防止对办法应用无意义或者不置可否的动词
防止无意义或者不置可否的动词。有些动词很灵便,能够有任何意义,比方 HandleCalculation(),processInput()等办法并没有通知你它是作什么的。这些名字最多通知你,它们正在进行一些与计算或输出等无关的解决。
所用的动词意义含糊是因为办法自身要做的工作太含糊。办法存在着性能不清的缺点,其名字含糊只不过是个标记而已。如果是这种状况,最好的解决办法是从新结构这个办法,弄清它们的性能,从而使它们有一个分明的、准确形容其性能的名字。
Case1: 徒有虚名的 process
代码示例:
/**
* 解决主图的数据
*
* @return 如果有浮层数据就返回 true,没有就返回 false
*/
private boolean processMainPic() {
....
boolean hasMainPicFloat = false;
....
return hasMainPicFloat;
}
// 调用处
boolean hasMainPicFloat = processMainPic();
大脑流动:
1、办法名的字面意思是解决主图(暂不纠结缩写 Pic 了),然而是如何解决主图的呢?
2、返回值是 bool 类型,是示意解决胜利或失败吗?
3、查看正文解释,以后办法是在解决主图的数据,返回为是否存在浮层数据,为什么一个解决主图数据的办法查看的是浮层数据呢?
看完发现,这个办法原来是拿主图数据查看其中是否存在浮层数据,徒有虚名呀。
批改倡议:
额定阐明:既然工程默认“Float”是浮层,这里不做额定批改,但实际上不合理,毕竟 Float 在 Java 中示意浮点型数据类型,会引起误会。
/**
* 是否有浮层数据
*
* @return 如果有浮层数据就返回 true,没有就返回 false
*/
private boolean hasFloatData($MainPictureData) {
....
boolean hasFloatData = false;
....
return hasFloatData;
}
// 调用处
boolean hasFloatData = hasFloatData(mainPictureData);
Case2: 我该如何正确应用这个办法
代码示例:
// 10 多处调用
... = GatewayUtils.processTime(System.currentTimeMillis());
public class GatewayUtils {
....
// 这个办法没有正文
public static long processTime(long time) {return time + (SDKUtils.getTimeOffset() * 1000L);
}
....
}
大脑流动:
好多中央调用工具类的 processTime,processTime 到底是在解决些什么呢?
如果入参传入的不是 System.currentTimeMillis() 而是 SystemClock.uptimeMillis() 或者随便传入一个 long 值,办法的返回值会是什么呢?
批改倡议:
public static long currentNetworkTime() {return System.currentTimeMillis() + (SDKUtils.getTimeOffset() * 1000L);
}
7.2 防止返回和办法名定义不统一的类型
Case1: 公有办法就能够乱定义吗?
码示例:
// 惟一调用处
final IPageProvider pageProvider = checkActivityAvaliable();
if (pageProvider == null) {
....
return;
}
// 函数申明
private IPageProvider checkActivityAvaliable() {IPageProvider pageProvider = pageProviderWeakReference.get();
if (pageProvider == null) {PopFactory.destroyPopCenter(pageName);
return null;
}
return pageProvider;
}
大脑流动:
check 办法如果有返回值的话不应该是 bool 类型吗?
“Avaliable”拼错了诶,正确的单词拼写是:“Available”。
“IPageProvider”和“ActivityAvaliable”是什么关系,为什么校验可用的 Activity 返回的是“IPageProvider”。
代码跟读:
原来办法外面偷偷做了一个销毁“PopCenter”的动作。把获取“PageProvider”和销毁“PopCenter”两件事件放在了一起。的确没看懂办法名和办法所做任何一件事件有什么关系。
批改倡议:
干掉 checkActivityAvaliable()办法。(这里不展开讨论高质量的函数相干内容)
final IPageProvider pageProvider = pageProviderWeakReference.get();
if (pageProvider == null) {PopFactory.destroyPopCenter(pageName);
....
return;
}
04 养成良好的命名习惯一些倡议
1. 对本人严格自律,本人写代码时要有一种心愿把每个名称都命名好的强烈意识和严格的自律意识;
2. 要致力剖析和思考以后被你命名的事物或逻辑的实质;这点十分要害,思考不深刻,就会导致最初对这个事物的命名谬误,因为你还没想分明被你命名的事物是个什么货色;
3. 你的任何一个属性的名字都要和其理论所代表的含意统一;你的任何一个办法所做的事件都要和该办法的名字的含意统一;
4. 要让你的程序的每个类似的中央的命名格调总是统一的。不要一会儿大写,一会儿小写;一会儿全称一会儿简写;一会儿帕斯卡 (Pascal) 命名法,一会儿骆驼 (Camel) 命名法或匈牙利命名法;