网站建设与维护 前台,温州网站升级,深圳网站建设推进,背景墙素材高清图片免费作者#xff1a;陈立(勤仁)
你可不能像给狗狗取名字那样给类、方法、变量命名。仅仅因为它很可爱或者听上去不错。
在写代码的时候#xff0c;你要经常想着#xff0c;那个最终维护你代码的人可能将是一个有暴力倾向的疯子#xff0c;并且他还知道你住在哪里。
01 为什么…作者陈立(勤仁)
你可不能像给狗狗取名字那样给类、方法、变量命名。仅仅因为它很可爱或者听上去不错。
在写代码的时候你要经常想着那个最终维护你代码的人可能将是一个有暴力倾向的疯子并且他还知道你住在哪里。
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 ListSectionModel dataSet;大脑活动
“dataSet” 在最初一定是为了元素去重选择了Set类型肯定后来某一个历史时刻发现有bug被偷偷改成了List类型但是变量名没变。
代码跟读
跟踪提交记录呃在18年被刚定义时就是 List*** dataSet;
修改建议
private ListSectionModel dataList;
或者
private ListSectionModel 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个刷新方法保留一个刷新接口。
case24个数据源定义该用谁呢
代码示例
声明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);}....
}大脑活动
好多地方调用工具类的processTimeprocessTime到底是在处理些什么呢
如果入参传入的不是 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)命名法或匈牙利命名法