四个有害的Java编码习惯
原文作者:John O'Hanley
原文链接:Four harmful Java idioms, and how to fix them
翻译:Tanya
程
序中的编码风格让我们的编程工作变得轻松,特别是程序维护员,他们要经常阅读其他人编写的程序编码,这一点尤其突出。编码规范从根本上解决了程序维护员的
难题;规范的编码阅读和理解起来更容易,也可以快速的不费力气的借鉴别人的编码。对将来维护你编码的人来说,你的编码越优化,他们就越喜欢你的编码,理解
起来也就越快。
同样,高水平的编码风格(例如固定的封闭结构)目的在于改善设计和使编码更易于理解。事实上,最后有些人会认为改善设计和提高编码的易读性是一回事。
本文中你会看到一些流行的编码风格被面向读者的更易于接受的风格所替代。有人争论说这些风格都已经被大家广泛使用,不应该简单的为了达到读者的期望而抛弃。然而,读者的期待只是其中一方面的原因,不可能凌驾于所有因素之上。列出四种常见的问题:
1.对局域变量(local variables)、参数(method arguments)
、字段(fields)
这三种变量的命名没有区分:
对看编码的人来说,首先要弄清这些数据如何定义的?看一个类时,得弄清楚每个条目是局域变量?字段?还是参数?有必要使用一个简单的命名约定来定义这些变量,增加易读性。
很多权威机构规范过字段变量用以区分它与其它的变量,但这远远不够。可以把对字段的合理的命名约定逻辑也应用在参数上面。先看示例1:没有进行区分这三种变量的类定义,如下所示:
示例1:
public boolean equals (Object arg) {
if (! (arg instanceof Range)) return false;
Range other = (Range) arg;
return start.equals(other.start) && end.equals(other.end);
}
在这个方法中,arg直接用argument的缩写,虽然大家一看就知道这是参数了,但这种命名方式却丢失了参数代表的对象本身
的含义。大家知道这 是参数,却不知道这是什么参数。如果方法的参数多一点,都按照arg1,arg2这样的方式命名,阅读代码
的时候很头疼。另外两个字段变量,start和
end,突然凭空而出,想一下才知道这应该是字段。当然,这个方法很短,造成的困难还不大,如果这个方法比较长的话,突然看到start和end两个变
量,一般会先在前面找一下是不是局部变量,然后才能确定是类的字段变量。
这个问题貌似微不足道,但为什么要让代码阅读者花费额外时间在这些琐碎的问题上呢?如果有个方案能让代码阅读者一目了然的明白变量是那种变量,为
什 么不采用呢?就如同Steve McConnell在
《代码大全》中说的:"让人费神去琢磨神秘杀人凶手这没有问题,但你不需要琢磨程序代码,代码是用来阅读的。
接下来看示例2,使用命名约定后对示例1重写以后的代码,用到的命名约定有:
参数
定义
时名字加前缀a
字段定义时名字加前缀f
局域变量定义时不加任何前缀
示例2:对变量类型进行区分
public boolean equals (Object aOther) {
if (! (aOther instanceof Range)) return false;
Range other = (Range) aOther;
return fStart.equals(other.fStart) && fEnd.equals(other.fEnd);
}
你可能反对示例2中的风格,反对过时了的匈牙利符号,但是我认为反对是错误的,因为匈牙利符号能详细说明信息的类型。
上面的命名约定区分了类型。而且这样做分清了字段、变量和局域变量,这是两种完全不同的概念。
这种命名约定的方式并不像看起来那么微不足道:当这些约定用在程序编码中时,会大大降低理解的难度,因为你可以不需
要先分辨这些变量,省去不少时间。
2.按层次划分包
,而不是根据特征或功能划分
最常见的划分应用序就是按层次命名包:
com.blah.action
com.blah.dao
com.blah.model
com.blah.util
也就是说,把具有同样特征或者功能的类划分到了不同的包里。因为成员的属性对其他成员应该是可见的,这就意味着几乎应用程序中所有的
类都是公共的。实际上,这种按层次划分包的方法完全扔掉了Java的包内私有。包内私有应该彻底不使用。现在,包内私有是Java程序语言中设计者的默认
作用域。这种包的划分习惯也违反了面向对象编程的核心原则之--尽量保持私有以减少影响,因为这种习惯强迫你必须扩大类的作用域。由于一些奇怪的原因,一
些Java组织不赞成这种命名,似乎不公正的。
另一种风格是按特征划分命名:
com.blah.painting
com.blah.buyer
com.blah.seller
com.blah.auction
com.blah.webmaster
com.blah.useraccess
com.blah.util
这里,成员不按行为划分,而是按照不同特征的类划分,每个成员都关联不同的特征。这种方法下包在最初使用是被定义。
例如:在Web应用程序中,“com.blah.painting
”包可能由下列成员组成:
- Painting.java: 一个model对象
- PaintingDAO.java: 一个数据存取对象Dao
- PaintingAction.java: 一个控制或者行为对象
- statements.sql: Dao对象使用的SQl文件
- view.jsp: Jsp文件
需要特别说是的是,这种划分方法,每一个包都包含所有成员有关的特征文件,而不仅仅是Java源文件。这种按特征划分包的方法,要求在做删除操作时要注意,删除一个特征时要删掉它的整个目录,不能保存在源码中。
这种方法优于按层次划分包的方法,表现在以下几点:
-
包是高内聚的,并且模块化,包与包之间的耦合性被降到最低。
-
代码的自描述性增强. 读者只需看包的名字就对程序有些什么功能或特征有了大概的印象。在《代码大全》中,
Steve McConnell 将自描述性的代码比作 "易读的圣杯",来表达它的易读性。
-
把类按照每个特征和功能区分开可以很容易实现分层设计。
-
相关的成员在同一个位置。不需要为了编辑一个相关的成员而去浏览整个源码树。
-
成员的作用域默认是包内私有。只有当另外的包需要访问某个成员的时候,才把它修改为public.
(需要注意的是修改一个类为public,并不意味着它的所有类成员都应该改为public。public成员和包内私有(package-
private)成员是可以在同一个类里共存的。)
- 删除一个功能或特征只需要简单的删除一个文件夹。
- 每个包内一般只有很少的成员,这样包可以很自然的按照进化式发展。如果包慢慢变的太大,就可以再进行细分,把它重构为两个或者更多新的包,类似于物种进化。而按照层次划分的方式,就没办法进化式发展,重构也不容易。
一些框架推荐使用层层定义包的传统的方式做为包的命名方法:由于使用传统的包命名,开发者总能知道在哪个位置可以找到
这些项目,但是为什么避免人们这样做呢?使用另一种按特征定义包的风格,就不需要这种单调的操纵,因此,
按特征定义完
全超越了任何其它命名约定。约书亚布洛赫在《高效的Java》一书中说到:区分一个设计好坏的唯一重要因素是模块内部隐藏
的数据和其它模块中涉及的实现过程的程度。
3.习惯用JavaBeans而不是不可变对象
不可变对象是构造后状态不改变。Scala的主要创造者Martin Odersky最近还称赞过这种不可变对象。在《高效的Java》一书
中,Joshua Bloch列举了大量实例支持使用不可变对象,并总结了很多优点。但他的意见,似乎很大程度上被忽略。大多数程
序使用JavaBeans来替代不可变对象。JavaBean明显要比不可变对象复杂的多,因为它的巨大的声明空间。粗略的讲,你可以
把JavaBean看作是与不可变对象完全相反的对象:它允许最大的可变性。
JavaBean常被用来做数据库记录的映射。假如你要从数据库记录集映射一行为对象,不考虑现有的持久化方案和框架,你会将
这个对象设计成什么样子?跟javabean相似呢还是完全不一样?
我认为会完全不一样,说明如下:
- 它不包含一个无参数构造方法(这一特征是javabean必备的。)。作者认为一个数据库记录的对象如果不包含任何数据是没有意义的。一个数据库表的所有字段都是可选的情况有多少?
-
It would likely not
have anything to say about events and listeners.(不太明白作者的意思)
- 它不强迫你用可变的对象。
-
它内部有一个数据验证机制。这样一个验证机制对大多数数据库应用非常重要。(记住对象的第一原则:一个对象应该同时封装数据和对数据的操作。在这种情况下,操作就是验证数据。)
-
数据验证机制可以给最终用户(end user)报错。
按照javabeans的说明,javabeans是用来解决特殊领域的问题:在图形界面程序的设计中充当小部件。说明中绝对没有提到数
据库。但现在通常用javabean来做数据库记录的映射。从实际角度来讲,许多被广泛使用的框架要求应用程序使用JavaBeans(或者其它类似的规
范)来映射数据库记录。这种滥用不利于编程者了解和使用不可变对象。
4.私有成员排在其它成员的前面
类成员的排序没有按照成员的作用域的大小排列,而是把private放在前面。
以前的好莱坞影片开头总是长篇的荣誉。同样地,大多数Java类把私有成员放在最前面。示例3给出这种风格的典型例子:
public class OilWell implements EnergySource {
private Long id;
private String name;
private String location;
private Date discoveryDate;
private Long totalReserves;
private Long productionToDate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
//..elided
}
然而,如果把私有成员定义放在后面,读者阅读会更容易。因为人们认识一个事物的通常过程都是从一般到特殊,从抽象层次来说,是从高层次到低层次的认识过程。如果你倒过来的话,读者就不能从整体上把握事物,也不能抓住事物的本质,只能在一堆具体的片段中迷失。
整体的抽象让你忽略了细节。抽象的层次越高,你可以忽略越多的细节。读者阅读一个类时可以忽略的细节越多他会越高兴。脑袋里填充太多的细节是痛苦的,所以细节越少越好。因此,将私有成员放在最后会显得更富有同情心,因为这样阻止了不必要的细节显露给读者。
本来C++程序的习惯也是像Java一样把private成员放在最开始。然而,C++社区迅速的认识到这是一个有害的规范,这个规范现在已经被修正。这里给出一个经典的C++风格指南里的注释:
注意:public 接口应该放在class的最开始,其次是protected成员,最后是private成员。原因是:
- 程序员应该更关心接口而不是具体实现。
- 当程序员需要用一个类的时候,他们需要的是接口而不是实现。
把接口放在开始是非常有意义的。把实现部分,私有部分,放在开始是一个历史遗留问题。最后还是要反复强调一下,一个类的接口的重要性超过实现细节。
同样,伦敦大学帝国学院关于C++的指面中也说到:把公有的部分放在前面,读者会更感兴趣阅读,然后是保护的部分,最后是私有的部分。
有人会持反对意见,认为读者可以使用程序文档来理解类,而不是直接看源代码。这种理由似乎不成立,因为程序文档中没有相关的实现细节,这时看源代码是很有必要的。
所有的技术文档,通常都把难理解的信息放在开头,比如抽象的学术论文。为什么Java不打破这种常规呢?把私有成员放在最开头部分看起来是不是打破常规的好习惯。这种习惯似乎是sun早期的编码规范造成的。
将代码按照javadoc的顺序编排是非常好的:首先是构造方法,然后是非私有方法,最后是私有部分和方法。这样读者阅读的时候很自然的从抽象层次的高向低运动。
本文所讲的是一些Java的不好习惯和风格需要改变。最终的目地是希望我们的代码易读性更强,让读者更易于理解。
分享到:
相关推荐
codemirror版本:https://codemirror.net/5/doc/releases.html
外国电影演员识别系统源码分享
mf3010 打印扫描一体机驱动管理软件。
2024免费毕业设计成品,包括源码+数据库+往届论文资料 启动教程:https://www.bilibili.com/video/BV11ktveuE2d 讲解视频:https://www.bilibili.com/video/BV1YfkHYwEME 二次开发教程:https://www.bilibili.com/video/BV1Cw2rY1ErC
chrome-headless-shell-linux64-135.0.7004.0 (Canary).zip
DeepSeek大模型介绍与展望.pptx
英特尔的公版原理图和PCB,cadence版本
《单容水箱液位精准调控:模糊控制策略的深度研究与复现》,单容水箱液位随动系统的模糊控制研究 模糊控制lunwen复现 期刊:化工与自动化仪表(2021年) 图1为结构图,图9为原文结构图, 版本不一样,器件略有调整 图7为结果图,图8为原文结果图 ,单容水箱液位;模糊控制;研究;论文复现;期刊;化工与自动化仪表;结构图;结果图;版本差异;器件调整,"模糊控制研究在单容水箱液位随动系统中的应用与复现"
一个windows上使用的搜索小工具
内容: 这份数据集包含了来自国际大洋发现计划(IODP)第342航次站点U1405、U1406、U1407、U1409和U1410的浮游有孔虫碳酸盐团簇同位素、稳定氧和碳同位素,以及沉积物中的GDGT(甘油二烷基甘油四醚)和烯酮数据。这些站点位于北大西洋的新foundland脊(U1407、U1409和U1410)和J-异常脊(U1405和U1406),用于创建覆盖整个新生代的几乎连续但低分辨率(约每92万年一个样本)的数据拼接,并重建了碳酸盐团簇同位素、TEX86和UK'37海表温度。每个样本包含20立方厘米的沉积物,覆盖2厘米的核心深度区间。年龄模型主要基于详细的船上生物-磁性地层学研究(Norris等,2014)。然而,在40.8 Ma至44.8 Ma时间段内,使用了Cappelli等人(2019)更新的U1410站点年龄模型,通过与U1408站点的年龄模型对比来确定。 访问此数据集,请点击这里:"" ()。
厨房用品分割系统源码&数据集分享
.
监控鞋类物品检测系统源码分享
2024免费毕业设计成品,包括源码+数据库+往届论文资料 启动教程:https://www.bilibili.com/video/BV11ktveuE2d 讲解视频:https://www.bilibili.com/video/BV1YfkHYwEME 二次开发教程:https://www.bilibili.com/video/BV1Cw2rY1ErC
曲线图异常波形检测系统源码分享
内容概要:本文介绍了动车组车号自动识别的现状及其存在的问题,提出了基于图像识别技术的新方法。文中详述了传统人工识别与RFID识别方法的不足,重点阐述了一种新的图像识别系统的设计与实施方案,该系统能够实现在多种恶劣环境下高效精确地获取动车组车号,并通过实际案例展示了这套系统的优势以及其在铁路行业的广阔应用前景。 适用人群:从事铁路运输管理、轨道交通系统开发维护的技术人员,尤其是负责动车组调度、监控及维修工作的相关人员。 使用场景及目标:①用于替代现有人工记录与RFID标签方式,提升动车组车号识别精度与效率;②适用于各种天气状况下的户外作业场景;③旨在构建更加智能化、信息化程度更高的铁路运输体系,助力智慧动车段建设。 其他说明:文中还包括具体的实验对比和技术细节分析,如不同的开机触发装置选择、图像采集设备参数设置、补光措施及识别算法的设计,强调了实际应用场景中可能遇到的问题以及相应的解决方案。
基于AnythingLLM框架和Ollama环境本地运行deepseek,并可以通过用户自己的文档来针对性地回答用户问题,用户也可以上传文件来构建模型回复问题所需要的所有参考资料的知识库,使得模型相对于在线模型更加专业地解答用户的问题。同时本地部署保证了隐私性和针对性。
指针式表盘指针关键部位分割系统源码&数据集分享
多策略增强:MWOA鲸鱼优化算法与其他变体及2024年最新算法的实证比较与结果分析——新颖策略实施效果显著且复杂度无增加的研究,多策略改进的鲸鱼优化算法(MWOA),与其他三种变体和几种2024最新算法比较,策略都是很新颖的策略,可以直接写了发文章,并且没有增加复杂度上改进效果 ,MWOA; 变体算法; 最新算法; 策略新颖; 复杂度未增加; 改进效果显著,"多策略改进MWOA算法:与多种变体及2024新算法比较展示优越性"
织物缺陷检测系统源码分享[一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示]