因此,我们有一个喜欢编写将Objects作为参数的方法的人,因此它们可以“非常灵活”。然后,在内部,他要么直接进行强制转换,反射,要么方法重载来处理不同类型。

我觉得这是一个不好的做法,但是我无法确切解释原因,除了它会使它更难以阅读。还有其他更具体的原因会导致这种做法不好吗?那会是什么?


所以,有些人要求举个例子。他定义了一个接口,如下所示:

public void construct(Object input, Object output);


,他计划通过将多个列表放入列表中来使用这些接口,因此它会构建一些位并将其添加到输出对象,就像这样:调用它,传递输入/输出。

我一直告诉他,它有效,我理解它有效,但是将所有内容放在一个类中也可以。我们正在谈论重用和可维护性,我认为他实际上是在约束自己,并且重用少于他的想象。可维护性虽然现在对他来说可能很高,但将来对他和其他任何人来说可能都非常低。

评论

如果他坚持要像使用Python或Ruby一样对Java进行编程,那么他就不应该使用Java进行编程。

就我个人而言,我认为这是“字符串型”代码的变体-缺点大致相同。

即使在动态类型的系统上,这听起来也很糟糕,而且完全无法维护。多态(无论是动态的,静态的,基于类的,基于鸭子的,等等)的想法是让不同的实现执行不同的事情,而不是区分类型和不同的操作。

@Mark:我敢打赌他看到了很多类似AppendNumberToString(Object,Object)的方法,而不是AppendNumberToString(String,Int)。显然,如果该方法是公共的,则没有关于应该首先提供哪个参数的信息。在第二种情况下,该方法是自记录。

我通常会看到人们试图像编写Java一样编写python,而不是反过来...

#1 楼

从实际的角度来看,我看到了以下问题:


可能出现运行类型错误的膨胀-除非使用Java包含的强类型检查器可以避免很多动态类型检查。
很多不必要的强制转换
很难理解方法通过其签名执行的操作

从理论的角度来看,我看到了以下问题:


缺乏类接口的契约。如果所有参数均为Object类型,则您未在声明任何有关客户端类的信息。
缺乏重载的可能性
重写的不正确性。您可以重写方法并更改其参数类型,从而破坏与继承相关的所有内容。


评论


我把第二点误读为“很多不必要的猫”。我本来要询问那只猫是必需的,但意识到这没有任何意义。

–巴尔
13年5月15日在12:55



@bharal,好吧,如果将所有内容都键入为对象,则猫与其他任何对象一样有效。

– Kyralessa
15年6月26日在20:28

另外,完全丢弃所有有用的IDE自动完成功能。丰富的类型信息使您可以非常快速地生成/插入正确的值。更不用说功能接口了;您不能将()->“ do” + stuff()传递给对象...

–阿贾克斯
18-10-4在8:28

@bharal好吧,我想我们现在需要一个@Meowable类型注释...

– varun
19-4-9在12:17



#2 楼

该方法违反了Liskov替换原理。

如果方法接受类型为Object的参数,我应该期望传递给该方法的任何对象都可以使用。

听起来像是只允许Object的某些子类。知道允许哪些类型的唯一方法是知道方法的详细信息。换句话说,该方法不如所宣传的那样灵活。

评论


是的-几乎所有SOLID原理都已从此处弹出窗口...

–罗比·迪(Robbie Dee)
13年5月14日在19:48

除非您准备捍卫所有带有无类型变量的语言都违反Liskov的论点,包括Liskov自己提出时正在使用的语言,否则这不会违反任何这样的原则。

–卡兹
13年5月14日在20:31

@Kaz,你是什么意思?具有弱类型的语言不是无类型的,我认为Liskovs点与编译器类型检查没有任何关系。如果我创建了一个名为def add_user(user)的Ruby方法,那么显然不能将Message实例,Hash或File处理程序放入该方法中-该方法可能需要一个User实例。编译器是否强制执行此操作无关紧要。但是,如果我定义def Construct(object),我希望该方法能够处理所有可想象的对象……显然,这不符合OP的要求。那是违规。

– Niels B.
13年5月14日在20:52

好吧,看来我们不同意。通过其签名声称声称接受它不接受的任何方法的方法编程不良,违反了Liskovs替代原理。构造(对象输入,对象输出)必须接受对象的任何子类-意味着除null外的所有可能参数。

– Niels B.
13年5月14日在22:21

这并没有违反Liskov替换原理,除非您准备说每个接受int参数的方法都必须接受负数和超出范围的值,等等。另外,在公共API中使用接口的人也可以隐藏信息然后接受接口作为参数,但期望它们成为自己的内部具体类型也违反了该原理。我的理解是,该原则说,方法的覆盖必须至少接受相同类型的输入,并导致最多相同类型的输出。

–安德鲁·阿诺特(Andrew Arnott)
2013年9月22日15:22

#3 楼

我会考虑以下直接含义:
可读性
我想和他一起工作并不是世界上最愉快的经历。您永远都不知道类型到底是什么,或者参数会发生什么。我怀疑您是否会浪费您的时间。
类型签名
在Java中,一切都是对象。那么,该方法最终会做什么?
速度
反射,强制转换,类型检查等都会导致性能降低。为什么?因为在他看来,它使事情变得灵活。
反对自然
自诞生以来,Java一直是强类型的。告诉他开始编写JavaScript或Python或Ruby(如果他想要动态键入的方言)。他的背景可能很强地基于其中之一。
灵活性
要使用与所讨论的同事相同的术语,Java很早就已经意识到了灵活性。用动态类型化的语言戴上马眼镜是没有意义的。
接口,多态性,协方差和反方差,泛型,继承,抽象类和方法重载只是其中的一部分有助于所谓的“灵活性”。灵活性是因为几乎可以将任何给定类型的任何事物都专门化(通常情况是专门化直接的上位类型或下位类型)。
文档
主要目标之一编程的目的是编写可重用且稳定的代码。您的示例文档的外观如何?能够使用该开发人员的另一个开发人员的学习时间是多少?我建议您编写可修改的汇编代码,并对此负责。

#4 楼

这里已经有很多好的答案。我想补充一个辅助思想:

这种模式很可能是“代码异味”。

换句话说,如果您需要将Object传递给方法以实现“最终的”灵活性,则很可能使用的方法定义很不完善。具有强类型化的参数表明对调用方是“合同”(我需要看起来像“ this”的东西)。无法指定此类合同很可能意味着您的方法执行了太多操作或定义的范围不正确。

评论


这相当于微型波咖喱的编码器。

–迈克尔·布莱克本
13年5月14日在20:14



@MichaelBlackburn:???我不明白那条评论?微波咖喱鱼有什么特别之处?谷歌搜索它,我只会得到有趣的(如果您喜欢鱼)食谱甚至视频。

– Marjan Venema
13年5月15日在6:22

@MarjanVenema Microwaving鱼咖喱会很快产生很多鱼味。

–乔纳森
13年5月15日在7:07

@Kaz类型转换决不会使Java“更具动态性”。它只是将类型的硬编码隐藏在方法内部,而不是在方法的签名中使类型显式,从而使代码更脆弱且更难以理解。它如何变得更有活力,还是一件好事?

– Andres F.
13年5月15日在13:12



@卡兹不同意。对方法中的类型转换进行硬编码并不像让一种语言(例如Python)来处理它。此外,如果您的方法由于类型转换不正确而崩溃并烧毁,那么它如何更“动态”?您正在颠覆Java的编译时检查,却一无所获。

– Andres F.
13年5月15日在17:52



#5 楼

当对象转换为具体类型时,它消除了编译器进行的类型检查,并可能导致更多的运行时异常。因此,最终用户将看到未处理的异常,除非所有无效的强制转换都被捕获并正确处理。

评论


这...他正在丢弃静态类型检查。这意味着程序将在运行时而不是编译时失败。那是一件坏事。语言具有类型。使用它们。忽略它就像调用所有变量var1,var2,var3等。确定它会起作用,但这不是一个好主意

– JohnB
13年5月15日在7:35

#6 楼

我同意其他回应,认为这通常是不好的做法。

在一种特定情况下,我发现传递Object可能更好:在最终处理字符串时,例如如果要创建XML或JSON。在这种情况下,我会选择:

public void addAttribute(String s) { 
  //... add s to the XML
}
在调用代码中。另外,它与String.valueOf配合使用也很好。并且,通过自动装箱,您可以传递varargints等...由于所有对象都有floats,因此可以说这并不违反Liskov等...

评论


+1表示执行此操作的几次实例之一-因此是证明规则的例外。

–蓬松
13年5月14日在21:01

“证明”一词的原始含义是“测试”。

–艾伦B
13年5月15日在8:18

提出的问题多于答案。您为什么要制作一个用于处理XML的API?为什么它允许任何东西不受限制地进入?

–洛伦佐·博卡恰(Lorenzo Boccaccia)
2013年9月23日15:32



我将其称为API增强功能,在其下方可能调用了一些更标准的XML包。不确定我是否完全理解您的问题。

–user949300
13年11月14日下午4:37

#7 楼

是。 java应该严格按照typecast ed进行。但是将Object作为参数实际上会违反该规则。此外,这将使您的程序更容易出现错误。

这就是为什么引入generics的主要原因之一。

#8 楼

灵活且能够处理多种类型的数据正是创建方法重载和接口的目的。 Java已经具有使通过类型检查确保编译时安全性的同时简化处理这些情况的功能。

此外,如果不可能狭义地定义该方法所处理的数据,则该方法可能正在做太多,需要分解为较小的方法。

我喜欢我所读过的一条经验法则(我认为在Code Complete中)。该规则指出,您只需阅读其名称即可知道一个函数或方法的功能。如果确实很难使用该想法明确命名函数/方法(或必须使用And来命名),则该函数/方法确实做得太多。

#9 楼

首先,如果将参数作为具体对象传递,则将在编译时检测到使用错误对象的任何错误(非预期类的实例),因此可以更快地进行修复。在另一种情况下,该错误将在运行时显示,并且将更难检测和修复。

其次,检查实例的真实类,反射等的代码将比直接致电

#10 楼

我遵循的规则是...参数定义应与方法所需的一样具体,且不应多于或少于此。

如果我需要迭代一个可枚举的对象,则将参数定义为可枚举的对象,而不是数组或列表。这允许使用任何类型的枚举,避免转换为特定类型。

我也不会将其定义为对象,因为我需要一个枚举。不是非枚举的任何对象将无法在此方法中工作。读取方法签名并且不知道其含义是令人困惑的,只有运行时测试才能发现错误。

#11 楼

他所做的只是放弃了拥有静态类型系统的所有优点。

简单地说:对于编译器和阅读代码的人来说,这种方法的作用还不清楚。

静态类型系统的最大原因之一是对程序的运行具有编译时保证。编译器可以检查是否正确使用了一段代码,如果不正确,则发出编译时错误。像您同事一样编写的方法更容易发生运行时错误,因为缺少编译器时检查。准备运行时错误并编写更多测试。

另一个大问题是,根本不清楚方法的作用。读取,维护或使用这样的代码是一个很大的麻烦。

Java添加了泛型以提供对代码的更强编译时保证,Scala的功能方法和复杂的类型正在进一步发展具有协变和反变类型的系统。您的同事正在落后于这条发展线,放弃了它的所有优势。

如果他改用正确类型的,重载的方法,他(和您所有人)将获得:


检查编译器时间,以确保这些方法的正确使用。
明确指出允许哪些参数组合,哪些参数不允许。
可以正确地单独记录这些组合中的每一个。
代码的可读性和可维护性。
这将有助于他将方法拆分为更小的部分,从而更易于调试,测试和维护。 (另请参见如何说服开发人员编写简短的方法?。)


评论


@Kaz是的,我尊重他们的观点,但是Java对于这种方法而言是一个非常糟糕的选择。

–石油
13年5月14日在21:48

@Kaz:我想每个人都可以同意静态类型系统具有某些优点。并非所有人都同意这些优点是否胜过相反方法的优点。但是该开发人员并未获得相反方法的优势。 (没有鸭嘴兽,没有隐式打字,没有简洁。)他牺牲了静态打字的所有好处,同时几乎不损失任何成本。

–ruakh
13年5月15日在2:54

@Kaz太荒谬了。如果您使用Java编程,请遵循Java约定。如果您使用Python编程,请遵循Python的编程。并非所有人都认为动态类型系统的论点是“坚实的”。

– Andres F.
13年5月15日在12:06



@Kaz如果您想根据某种语言的惯例编写实验性代码,则一定要自己做。如果与团队合作,请遵循约定。就这么简单。同样,您正在将这变成一场关于创造力的辩论,这是荒谬的。如果您将锤子用作锤子,而不是用作螺丝刀,那么您的创造力不会降低。您正在使用工具来达到最佳效果。

– Andres F.
13年5月16日在12:14



@Kaz但是Java并不是一种动态语言,如果您使用Object,那么您(程序员)以后会在想要使用该值时被迫进行显式转换。因此,在Java中,使用Object是个坏主意。不是用Python,也不是您想使用的任何动态语言,而是用Java,这就是这个问题的目的。

– Andres F.
2013年9月20日18:52



#12 楼

实际上,我曾经不得不像以前那样处理过一个程序包,它是一个GUI程序包-设计师说他来自Pascal背景,认为除了基类之外,到处传遍都是一个很棒的主意。 > 20年来,使用此工具包是我职业中最烦人的经历之一。

直到您不得不使用类似代码的代码,您都不会注意到自己在参数中对数据类型的依赖程度如何。如果我说我有一个方法“ connect(Object dest)”,那是什么意思?即使您看到“ connect(对象网站)”并没有多大帮助,但是“ connect(字符串网站)”或“ connect(URL网站)”您甚至不必考虑。

您可能会认为“ connect(Object website)”是一个很棒的主意,因为它可以使用字符串或URL,但这仅意味着我必须猜测程序员决定支持哪种情况,而他不支持。最好的情况是,它将开发过程中的大多数试验和错误部分从编码时间移到了编译时间,最坏的情况是,它会使整个开发过程变得混乱而烦人。 )不断检查API调用链,方法是检查构造函数的一组参数,以查看要使用的构造函数,然后如何构建需要传递的对象,直到到达拥有或可以制造的对象为止-就像把难题拼在一起而不是在笨拙的文档中查找不完整的示例(我在Java之前的C语言使用经验)。 >
我知道已经回答了这个问题,但这真是令人讨厌的经历,我真的很想确保尽可能少的人再次经历它。

评论


该问题的解决方法是“ connect_httpd(Object url)”。看起来,无数程序员使用缺乏声明的语言来完成工作:具有类型推断的静态语言以及动态语言。数以百万计的轶事证据远胜于一个传闻。您在争辩说,由于错误选择了函数和参数的名称,并且将保持这种状态,因此我们应该声明类型。那么,是什么阻止我也拥有非对象的无用命名类型? “模糊(Widget foo)”你听不懂吗?该功能模糊了小部件!它输入了所有内容!

–卡兹
13年5月15日在16:38

我曾经在Ruby和Groovy工作过。尽管我喜欢在其中编写小任务,但是在处理文档记录不佳的库或与相邻团队(您没有联系过)的代码集成时,它们会令人恐惧。原因是代码中的信息不足。人们也可以使用Basic,Fortran,Cobol和机器语言来完成工作-都有自己的位置,很多都有优点和缺点,但是程序员“完成工作”并不是为给定任务选择任何一个的好理由。

– Bill K
13年5月15日在17:12

“他们太恐怖了”不是事实,只是您的轶事经历。人们可以用Fortran和机器语言来完成工作,但是人们可以使用动态语言来更快,更轻松地完成工作。几分钟之内可以在Ruby中完成的某些事情将花费您几个月的Java时间。

–卡兹
13年5月15日在17:24

人们用Ruby和动态语言更快地完成小事情-Twitter说。大事情没那么多(比如twitter)。有适合每项工作的工具,Ruby并不是所有工具的最佳解决方案-您是否建议这样做?

– Bill K
13年5月15日在17:29

#13 楼

要给出某种类型,请给出:


用户使用语义描述进行编码的接口。
编译器从其解析的接口允许其优化器增加代码执行时间或
早期的(静态)绑定在开发周期的早期就捕获了代码错误。

仅使用一个对象几乎无法传达任何信息,因此删除了所有这些原本可以保留的功能。出于上述原因而将其放入语言中。

此外,类型系统的设计还具有灵活性,它的is-a关系允许所有这些条件。因此,利用它来发挥您的优势。不要通过编写无法维护的代码来浪费它。

可能有一个特定的原因在特定的时间做这样的事情,但是总的来说一直这样做是不好的做法。如果您正在执行此操作,请让其他人查看该问题,看看是否可以通过其他方法解决。在大多数情况下(99.999%)都可以。

不可能达到的0.001%,至少你们所有人都知道并知道发生了什么(尽管您可能只需要对这个问题有一些新鲜的看法。

#14 楼

是的,这是很差的做法,因为它无法保证该方法将能够对传入的任何特定输入执行所需的操作。使得方法可广泛使用的目标并不是一个坏目标,但是有更好的方法

对于这种特殊情况,听起来像是使用接口的理想情况。接口将确保传递给该方法的对象提供所需的信息,并避免转换。在某些情况下,此方法不能很好地工作(例如,如果它必须与不能添加接口实现的密封类或基类一起使用),但是总的来说,方法应仅接受对其有效的输入,例如参数。

#15 楼

到目前为止,有很多深入的答案,我真的不得不说,甚至在进入高级哲学和方法论之前,它们都是懒惰,简短和简单的。

它们并没有在代码中提供良好的信息。供其他人维护和理解,并且它们与现有编程语言的设计背道而驰。我爱PERL,我爱Objective-C。但是,我不会为此而努力使Java,C#或C ++偏向于我的喜好,以牺牲可维护,可读和高效的代码为代价。

是的,如果他们需要改变方式函数起作用或输入是什么,它们可以在此处保存一些行更改和一些重构步骤。但是,其他人不得不维护代码的代价更高,更不用说大多数现代的IDE,例如eclipse和Xcode都可以通过简单的右键单击命令自动完成重构。此外,正如其他人提到的那样,每次转换,传递的每种未知对象类型都增加了处理成本,而且我想通过知道适当的类,运行时将执行的基本内置加速也抛在了窗外。 >
对于开发人员来说,语法糖总是比盐更好,但是两者的均衡饮食可以确保项目和其他相关人员(客户,应用程序,其他开发人员等)的健康。

#16 楼

这没有问题。在没有类型变量的语言中,这是每个函数都会发生的情况。形式参数只是值,类型是在传递的对象中,而不是形式参数中。

人们在Lisp,Python,Ruby,...中使用Stuff Done(TM) />
尽管您从编译时类型检查中受益较少,但也从编译时类型检查中受益较少。

真正的解决方案是使用一些针对Java虚拟机的动态语言,而不是编写丑陋的Java代码(其中每个第三个标识符都是单词Object)。

我确实有一个名为construct的完全通用的函数,该函数可以接收任意对象。

我想和这个程序员合作很开心。

请注意,这种类型的代码出现在C中,是对以C实现的动态语言进行运行时支持的实现中的C.您可能会在用C实现的Lisp中看到以下内容:

/* Map each object in a list through a function, in order to produce
   a list of the return values. */

val mapcar(val function, val list)
{
   val iter = car(list);
   val out = nil;  /* nil is really a null pointer */

   for (iter = car(list); iter; iter = cdr(iter))
      push(funcall_1(function, car(iter)), out);  /* push is a C macro */

   return nreverse(out); /* destructively reverse list */
}


val类型实际上是这样的:

typedef struct object *val;


struct object是一个复杂的结构,它覆盖了各种对象的各种类型信息。

我开发了一种非常不错的小型编程语言,其内部结构都是以此样式完成的,我特意尽可能保持简洁:与采用类似方法的某些其他语言实现的内部相比,这更是如此。

以这种风格,少量的C代码可能实现令人惊奇的事情。

评论


-1除了在OP中明确指出,程序员不是在进行鸭子输入,而是在某些情况下实际上转换为“正确”类型。在使用动态类型的语言(例如Python,Ruby等)中,这根本不是推荐的做法。相反,无论类型系统如何,这都是对OOP工作原理的深刻误解。

– Andres F.
13年5月15日在12:04



@AndresF是否可以以所有必要的方式使用对象,而无需获取对该对象的更具体类型的引用? Python和Ruby是否有相同的想法:变量是对基的引用,必须进行强制转换才能调用子类操作?

–卡兹
13年5月15日在16:05

我认为您不必显式地使用Python或Ruby进行转换(就我所知,这与他们的哲学背道而驰)。例如。建议您在Python中使用Duck Typing;如果您是演员,那说明您做错了。在Java中,如果您收到一个Object,然后将其强制转换为更合适的子类型,那么您做错了!您正在颠覆Java的静态类型检查,以获取任何回报。您实际上是在说“我知道如何使用它,但是我不想让编译器知道。只是因为。”

– Andres F.
13年5月15日在17:56



Python强制转换是将值转换为另一种类型:本质上是根据另一种值构造新值。例如,可以将整数强制转换为布尔值,但这意味着构造的布尔值对于非零整数为true,否则为false。这与为获取对象的完整类型而强制转换静态引用不同。我认为Python没有像Java引用那样的东西,所以说这不是推荐的做法会引起误解。这是不可能的做法。

–卡兹
13年5月15日在18:28

但是,当然,必须在Python解释器内部进行强制转换,以便可以对对象进行适当的操作。如果某个对象进入字符串函数,则解释器必须验证它是否为字符串,然后将指针转换为正确的类型以将其用作字符串对象。这是否意味着Python开发人员不了解OOP的工作原理?

–卡兹
13年5月15日在18:31

#17 楼

假设有一分钟,“对象家伙”有理由使用通用的“松散”类型的对象。我们不知道他的原因,但这使我想起了使用继承和泛型(模板)的价值。如果有机会,Object-guy实际上会尝试变得有意义,这就是他可能会试图变得有意义:

我总是将我的对象基于单个基础对象。这个基础对象负责处理其他任务,例如异常处理,日志记录,性能分析和垃圾回收。

我总是将数学对象(那些可能支持数学运算符的对象)基于单个基础类。这有助于提供实用程序,以在派生类不处理特定操作的情况下处理异常。例如,在事件中使用泰勒级数无法获得函数的显式表示。

所以我不知道Object-guys的原因是:也许这仅仅是因为他喜欢由他提供的动态类型其他语言。但是也许他正在尝试欣赏继承的价值。

他可能还想探索泛型(模板)。我在C ++中使用它们定义了二维,​​三维和四维图像,其中像素可以是字节,整数,浮点数,复数或光谱。我必须进行相当多的转换,但是在将图像的几何形状与像素的计算分离方面,回报是很重要的。

评论


类型转换不“继承”继承的值。这将OOP和多态性完全抛到了窗外(无论类型系统如何!)。

– Andres F.
13年5月15日在12:08



#18 楼

不要忘记,对于像JavaScript这样不支持按名称传递参数的语言,这是一个很好的模式。考虑一个函数

function dialog( message, cancelButton, okText, notOkText ) { ... } 


,该函数必须像

dialog( "...", true, "...", "..." );


那样调用,当您研究客户端代码时,要记住调用的第三个参数的含义并不容易。客户代码的可读性受到影响。因此,最好使用param对象声明它。

function dialog( options ) { ... }


它使您可以使用命名参数进行调用:

dialog({ 
  message : "...",
  cancelButton: true,
  okText: "...",
  notOkText: "..."
  })


原理:我们应该减少一个函数的参数数量(零是极好的,一个是频繁的,两个或更多个应该是稀有的)!

评论


Java代表JavaScript,汽车代表地毯。问题是关于Java的。此答案如何适用于该问题?

–user16764
13年5月14日在20:34

我喜欢汽车到地毯的比喻:D

–SinisterMJ
13年5月14日在22:57

@AntonRoth不是我的。

–user16764
13年5月14日在23:44

在Java中,直接等效项是dialog(Map options),而不是dialog(Object options),因此这不是OP的代码试图执行的操作。

– Esailija
13年5月15日在9:44



Java和JavaScript完全无关,因此这个答案确实很糟糕。

– Andres F.
13年5月15日在12:07