作者:陈立(勤仁)

你可不能像给狗狗取姓名那样给类、办法、变量命名。仅仅因为它很可爱或许听上去不错。

在写代码的时分,你要经常想着,那个终究维护你代码的人或许将是一个有暴力倾向的疯子,而且他还知道你住在哪里。

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)命名法或匈牙利命名法;