【.com速译】我将在本文中介绍几个错误。在使用集成开发环境(IDE)的工具自动生成代码时,经常会犯这些错误。自动生成的代码很棒,它使我们免于键入大量代码,并有助于专注于领域问题,而不是专注于示例代码的细节。然而,有时我们并没有仔细考虑IDE为我们生成的代码,我们留下了一些应该修复的小缺陷。我将在本文中逐一分析这些错误。公共领域IDE默认认为所有类都是公共的。所以,如果我们要求IntelliJ创建一个新类,就会发生这种情况:publicclassMyClass{}在我看来,默认情况下,类将具有包范围,即:publicclassMyClass{},它们在它们所属的包之外是不可见的。如果在某个时候我们需要更改类的域,请考虑原因并做出方便的设计决策。如果类默认是public类,我们直接使用,从而增加代码的耦合度。避免此问题的最佳方法是在我们的IDE中编辑模板。例如,在IntelliJIDEA中,此选项位于Preferences>Editor>FileandCodeTemplates>Templates>Classes中。当我们默认使用包范围时,最佳实践之一是它将接口创建为public但不公开其实现。这样,接口的客户不知道他们使用什么实现,因为他们甚至无法访问它,支持依赖注入等实践。公共领域这方面的另一个例子是IntelliJ默认创建公共变量:publicstaticfinalStringCONSTANT="value";大部分时间变量仅由拥有它们的类使用。请注意这一点,或更改您的模板。不可变类的细节与自动生成的代码没有直接关系,但我们99%的时间都忽略了它。一般来说,当我们想把一个类设计成不可变类时,结果会是这样的:}publicintgetId(){returnid;}publicStringgetName(){returnname;}}私有字段和final字段,没有setter方法,没有构造函数初始化。好的,那很好,但我们缺少一些东西。让我们看看如果我们这样做会发生什么:address=address;}}这个第二个类(继承自MyImmutableClass)的实例可以作为它的代理,客户端完全不用关注,即:MyImmutableClassmyImmutableClass=newMyImmutableDerivedClass(1,"name","address");这并不理想,因为派生类不是不可变的!解决办法很简单,我们不妨把不可变类做成final:publicfinalclassMyImmutableClass{//...}这样就不可能创建派生类了。Getters和setters我们经常创建包含所有字段的类,在自动生成代码的浪潮下,为所有字段添加构造函数、getters和setters。我们真的需要这样做吗?publicclassMyClass{privateCollaborator1collaborator1;privateCollaborator2collaborator2;publicMyClass(Collaborator1collaborator1,Collaborator2collaborator2){this.collaborator1=collaborator1;this.collaborator2=collaborator2;}publicCollaborator1getCollaborator1(){returncollaborator1;}publicvoidsetCollaborator1(Collaborator1collaborator1){this.collaborator1=collaborator1;}publicCollaborator2getCollaborator2(){returncollaborator2;}publicvoidsetCollaborator2(Collaborator2collaborator2){this.collaborator2=collaborator2;}}一个类应该公开最少数量的内部细节。所以,已经说过一个类在需要公开之前应该具有包范围,现在我要说的是除非需要,否则一个类不应该有getter或setter。如果发生这种情况,请考虑一下这是否是一个绝对的决定,或者您的设计中是否有任何不合适的地方。equals方法通常,IDE提供了同时生成equals方法和hashCode方法的选项。这是必要的,因为我们通常需要对这两种方法进行合适的、一致的实现(我不会在这里详细介绍,假设我们都知道equals和hashCode契约)。我不是很反对IDE生成的hashCode实现,它们通常是最好的,尽管我们可以进一步优化它们。但是,equals实现有一个小问题:publicclassMyDerivedClassextendsMyClass{privatefinalStringaddress;publicMyDerivedClass(intid,Stringname,Stringaddress){super(id,name);this.address=address;}publicStringgetAddress(){returnaddress;}//equalsandhashCode}这里的一切看起来都很好,但是发生了一些严重的事情:这个类违反了里氏替换原则。这个原则是SOLID原则的一部分;简而言之,就是“派生类可以充当基类,客户不会注意到”。我会用一个例子来解决这个问题,这样我们就可以理解为什么我们的equals方法有问题了。不妨创建一个派生类:publicclassMyDerivedClassextendsMyClass{privatefinalStringaddress;publicMyDerivedClass(intid,Stringname,Stringaddress){super(id,name);this.address=address;}publicStringgetAddress(){returnaddress;}//equalsandhashCode}我忽略了equals和hashCode实现,因为它们对于本示例不是必需的。publicstaticvoidmain(String[]args){MyClassobject1=newMyClass(1,"name");MyDerivedClassobject2=newMyDerivedClass(1,"name","address");System.out.println(areEquals(object1,object2));}publicstaticbooleanareEquals(MyClassobject1,MyClassobject2){returnobject1.equals(object2);}方法areEquals接收MyClass的两个实例。从包含的信息来看,这两个实例是完全一样的,所以我们认为areEquals会返回true。但其实不是,因为自动生成的equals方法执行了这个操作:if(o==null||getClass()!=o.getClass())returnfalse;getClass为第一个对象返回MyClass,为第二个对象返回MyClass返回MyDerivedClass,因此我们的方法equals返回false。我们可以讨论这种情况发生的频率,因为两者都是不同类的实例。但是,这两个字段中的所有字段都具有相同的值真的有意义吗?实际上,areEquals的替代实现可能如下所示:结果是真的。这就是里氏代换原则的大致内容。这一原则的含义之一是,如果我们重写一个方法,它会添加一些行为,而不会清除基类所做的任何事情。一个明显违背这个原则的做法是重写一个方法使其为空(我猜很多人都这样做过)。因此,我们需要为equals方法提供更好的实现。它应该是这样的:@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;if(!(oinstanceofMyClass))returnfalse;MyClassmyClass=(MyClass)o;if(id!=myClass.id)returnfalse;if(name!=null?!name.equals(myClass.name):myClass.name!=null)returnfalse;returntrue;}不是使用getClass,而是让对象知道它的类,我们做的是如果它是一个实例类的(equals驻留在运算符instanceof中),要求将对象作为参数传递给equals。对于此类及其派生类的实例,此运算符返回true。现在我们的areEquals方法终于按照我们想要的方式工作了。其实IntelliJ暴露了自动生成这个版本equals的方法,但是我们一定要慎重选择wizard程序的选项,因为默认是不勾选的,必须自己勾选。它是“接受子类作为equals()方法的参数”:使用自动代码生成工具(IDE),注意生成代码中的这些小故障,因为对话框本身提到该实现默认情况下违反了equals契约。Instanceof实现似乎与某些框架不兼容。在这种情况下,我们应该切换到第一个版本,但要时刻注意后果。因此,从正确的版本开始并在必要时切换到B计划可能是个好主意。原标题:Reviewyourauto-generatedcode作者:Raúlávila
