“太多参数”警告的解决方案

时间:2014-03-24 03:51:57

标签: java optimization parameters parameter-passing

在我的一些方法中,有Too Many Parameters并且很难维护和阅读  源代码。有时我担心“他们是否以适当的顺序传递适当的值?

我使用Checkstyle作为我的Eclipse插件,并且警告 超过7个参数

我不确定它可能只是一个编码标准,我不关心它。但是当通过视图服务 dao 传递许多参数时,我注意到它很难阅读并且稍后难以修改次。

所以,我试图用......传递这些参数。

  1. 许多对象 Bean 。但这给了我另一个问题,因为我的参数不能得到任何保证(不确定它们是否存在)

  2. HashMap 类型参数。但这可能会迫使我检查一些验证并尝试匹配方法调用方的

  3. 以上两种方法也可能会丢失编译时错误检查。 有没有减少参数计数的建议?

8 个答案:

答案 0 :(得分:13)

传递HashMap是非类型化脚本语言中的常见做法,但在Java中却是一种不好的做法。它击败了强类型的优势,这是您在Java中获得生产力的一部分。换句话说,Java编译器无法帮助您在开发过程中发现错误,而且您更有可能在运行时捕获它们。

如果您传递的参数在概念上相关,您可以在提及时将它们分组到适当的对象中。例如,如果您传递诸如firstName,lastName,dateOfBirth等参数,则可以传递具有这些属性的Person对象。这使用OO使您的设计更容易思考和维护。

如果我理解你对此的意思:“但是这给了我另一个麻烦,因为我的参数不能得到任何保证(不确定是否包含)”,你可以强制执行保证当你的Person或者等对象被实例化时你需要。一种方法是使用不可变的Person(等)对象:没有setter,而是通过构造函数传递所有params。如果它们都不正确,则抛出IllegalArgumentException。

祝你好运!

答案 1 :(得分:12)

有一些技术可以减少参数数量;

  1. 使用最小化的方法(将方法分解为多个方法,每个方法只需要一部分参数)
  2. 使用实用程序类(帮助程序类)来保存参数组(通常是静态成员类)
  3. 使Builder模式从对象构造适应方法调用。
  4. 尝试使用更好的架构设计减少单独包之间的数据流。
  5. 参考一些标准的java书籍;

    • Java:如何编程
    • Head First Java
    • Effective Java

    还尝试学习设计模式,它作为最佳编码实践非常有用。

    • 首创设计模式

答案 2 :(得分:6)

在我提出我的建议之前,让我先谈谈所提出的建议。我冒昧地跳过“臭”解决方案--HashMap和bean,因为你可以清楚地看到它们的缺点。

小心

  1. 盲目地使用辅助类来保存参数组。当团队凝聚力时(例如Mark Phillips的回答),他们可以真正发光,但是否则会导致问题(基本上就像打字的HashMap一样)。我怀疑它们是否适用于将7个参数从视图传递到DAO层的问题。

  2. 最小化的方法也很有意义,如果它们有意义的话(比如Effective Java book中的List示例)。我很少看到他们这样做的地方,所以我怀疑他们会解决你的问题。

  3. Builder模式通常非常干净,但它只解决了一层的问题 - 它没有告诉你如何进一步传递参数。当你从视图中获得一个参数时,DAO中需要这个参数,那么构建器只会膨胀你的代码,无论如何你都需要传递参数。

  4. 在我最终提出解决方案之前,让我挑战一个常见的隐含假设,即所有数据都需要以方法参数的形式通过堆栈传递。只有将处理对象应用于应用程序或会话范围时才会出现这种情况。在请求处理开始时创建所有相关对象时,约束消失。然后,您可以使用对象的构造函数仅向其传递必要的信息。

    最佳方式:使用请求生命周期的对象

    在某种程度上,这类似于方法对象或命令模式。

    为了应用此解决方案,您需要更改系统的入口点 - 通常这是某个视图层对象中的方法。让它负责两件事:

    • 请求对象图创建
    • 调用对象图root的执行/运行方法

    第一步至关重要。这是您从每个层构建请求范围对象的位置:视图,服务和DAO。对于每个对象,您只需将所需数据传递给其构造函数(例如,如果仅在DAO中需要参数“userIP” - 例如,用于审核DB访问,则仅将其传递给DAO请求对象)。请求对象还需要对其协作者的引用(如需要DAO的服务) - 相应地通过构造函数传递它们。

    第二步:当您设置对象图时,只需在第一个上调用execute / run方法(通常是视图层中的对象)。

    /** The example (in Scala) shows how your app's entry point could look like.
     *  The presented method belongs to an app-scoped view-layer object.      
     */
    def delete(itemId: Id, userIP: IPAddress) {
      // Note, that only RepositoryHelperReq class is interested in the 
      // "itemId" and "userIP" parameters
      val repoReq = MainRepositoryReq(RepositoryHelperReq(itemId, userIP))
      val serviceReq = MainServiceReq(ServiceHelperReq(repoReq))
      val viewReq = MainViewReq(ViewHelperReq(serviceReq))
    
      viewReq.execute()
    }
    

    现在让我回应一些对这种模式的预期批评。

    批评反驳

    1. 有人会说,性能会受到影响,因为堆上会有更多的对象来进行垃圾回收。 我会问那些测量,因为通常它不是对象分配,而是性能成本,而是对象保留(参见last presentation of Simon Ritter)。

    2. 有些人会询问应用程序或会话范围的数据,例如数据源或购物篮对象。这些对象仍然可以使用 - 您只需将它们注入到请求范围的对象中。

    3. 有些人会批评依赖结构,认为视图应仅依赖于服务而不依赖于DAO。这是有效的评论,只需注意,在经典的webapps中,你仍然有一个中心位置,这取决于所使用的每一层(通常被称为“世界末日”)。有时它是web.xml,有时它是Spring应用程序上下文或Guice模块。如果您关心正确的依赖关系,我建议您将所有工厂逻辑放在这样的位置,让它实现一些View-layer接口并注入视图。这样,您的整个依赖结构将保持干净和模块化。

    4. 有人会说,流行的DI框架(主要是Spring)支持这种模式非常糟糕。这是真的,你需要使用一个像样的DI库(Guice,Dagger for Java或Macwire,如果你喜欢Scala)或准备对抗Spring以做正确的事。

    5. 好处

      1. 没有长参数列表气味
      2. 没有不连贯的“请求上下文”对象(MagicContainer antipattern
      3. "stamp"-coupling - 中间层不需要依赖传递的参数,因此它们可以独立发布,更可测试且更清晰
      4. 数据仅在需要时使用 - 更容易测试,更少嘲笑
      5. 即使在其他方法失败的情况下也可以使用,比如当你没有内聚的ParameterObject来提取或者你不能轻易地将方法分成最小的正交方法时
      6. <强>积分

        MiškoHevery在他出色的博客文章How to do Everything Wrong with Servlets中向我介绍了这一特定问题的解决方案,并在Managing Object Lifetimes中精确定位(参见“更常见的违规”片段)。我想感谢他的帖子,因为很难在其他来源找到关于这个具体问题的准确指导。

答案 3 :(得分:4)

第一种方法是要走的路,它本质上是封装(OO的基本原则之一)。至于&#34;但这给了我另一个麻烦,因为我的参数不能得到任何保证(不确定是否包含)。&#34; - 这是一个非常常见的问题,并且已经使用JSR 303解决了。这是一个带有JSR 303验证注释的bean的一个非常基本的例子:

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

public class Book {

   @NotNull 
   private String title;

   @NotNull
   private String author;

   @Min(value=100)
   private int numOfPages;

   @NotNull
   private String isbn;

   ... ... ...
}

以下是验证方法:

Book book = new Book();
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Book>> violations = validator.validate(book);
for (ConstraintViolation<Book> violation : violations) {
   System.out.format("%s: %s%n",violation.getPropertyPath(), violation.getMessage());
}

这是运行验证的输出:

isbn: may not be null
numOfPages: must be greater than or equal to 100
author: may not be null
title: may not be null

进一步阅读:http://www.jroller.com/eyallupu/entry/jsr_303_beans_validation_using

但是,您不必进行此类手动验证,例如在Spring MVC中,您可以将@javax.validation.Valid放在传递给方法的bean上,它将自动验证。

答案 4 :(得分:2)

一般来说,我会说,尝试减少方法/类的责任以减少参数的数量。但如果真的需要它们,不要让插件阻止你。

答案 5 :(得分:2)

您必须能够通过以下选项解决此问题 1.构建者模式
2.伪命名参数方法将是一种很好的方法。(参考http://java.dzone.com/articles/named-parameters-java)(最适用于不可变对象)
3.命令模式。

只有在您使用方法中的所有7个参数时,上述选项才有效。 如果不是这个api的设计方式存在缺陷。

我在考虑设计方法和api时发现以下链接很有用,
http://www.infoq.com/presentations/effective-api-design

答案 6 :(得分:2)

通常会看到具有长参数列表的构造函数,特别是对于DAO。很多时候,我发现遵守类似的属性命名约定对于防止这种情况有很大帮助。

自从我编写了任何java代码以来已经有一段时间了,所以我需要传递一个java特定的例子。基本上,如果您具有相同名称和可分配类型的属性,则可以实现接受任何对象并将其映射到另一个对象的克隆函数。

在C#中,我在这里讨论过:Best way to clone properties of disparate objects

就类型安全而言,您可以控制命名约定。如果一个人总是意味着相同的东西,并且总是具有相同的类型,那么你将避免一些可能导致问题的常见陷阱。

如果我没有提到使用反射并不总是一个好方法,那将是我的疏忽。您需要考虑性能影响。另一个考虑因素是在持久化之前验证生成的对象。最后,要注意浅色副本。

答案 7 :(得分:0)

我不知道你的包是如何分开的,而当传输数据包含太多参数时,我的建议是在javaee中创建一个调用 DTO (数据传输对象)的新Object。你说,这不容易保证。否则,使用 CALLBACK 是一种更容易保证和扩展的更好方法。没有什么是完美的,你可以选择哪个方面是你的注意力。
回调被视为

void function(Callback callback){
    callback.do();
}

这是我的建议,但不是一个完全正确的方法。也许它对你有帮助。