让代码尝试不同的东西,直到它成功,整洁

时间:2010-11-02 07:42:05

标签: java heuristics

这是我第二次发现自己编写这种代码,并决定必须有一种更易读的方法来实现这一目标:

我的代码试图找出一些东西,这些内容并没有很好地定义,或者有很多方法可以实现它。我希望我的代码尝试几种方法来解决它,直到它成功,或者它用完了策略。但我还没有办法让这个整洁可读。

我的特殊情况:我需要从界面中找到特定类型的方法。它可以注释为显式,但它也可以是唯一合适的方法(根据其参数)。

所以,我的代码目前是这样的:

Method candidateMethod = getMethodByAnnotation(clazz);
if (candidateMethod == null) {
  candidateMethod = getMethodByBeingOnlyMethod(clazz);
}
if (candidateMethod == null) {
  candidateMethod = getMethodByBeingOnlySuitableMethod(clazz);
}
if (candidateMethod == null) {
  throw new NoSuitableMethodFoundException(clazz);
}

必须是更好的方式......

编辑:如果找到方法,则返回方法,否则返回null。我可以将其切换为try / catch逻辑,但这几乎不会使它更具可读性。

Edit2:不幸的是,我只能接受一个答案:(

7 个答案:

答案 0 :(得分:6)

对我而言,这是可读和可理解的。我只是将代码的丑陋部分提取到一个单独的方法(遵循“Robert C.Martin:Clean Code”中的一些基本原则)并添加一些javadoc(并在必要时道歉):

//...
try {
   Method method = MethodFinder.findMethodIn(clazz);
catch (NoSuitableMethodException oops) {
   // handle exception
}

以及稍后在MethodFinder.java

/**
 * Will find the most suitable method in the given class or throw an exception if 
 * no such method exists (...)
 */
public static Method findMethodIn(Class<?> clazz) throws NoSuitableMethodException {
  // all your effort to get a method is hidden here,
  // protected with unit tests and no need for anyone to read it 
  // in order to understand the 'main' part of the algorithm.
}

答案 1 :(得分:4)

我认为对于一小部分方法你正在做的事情很好。

对于一个更大的集合,我可能倾向于构建一个Chain of Responsibility,它捕获了尝试一系列事物直到一个工作的基本概念。

答案 2 :(得分:2)

我认为这不是一种糟糕的做法。它有点冗长,但它清楚地传达了你正在做的事情,而且很容易改变。

但是,如果你想使它更简洁,你可以将方法getMethod*包装到一个实现接口(“IMethodFinder”)或类似的类中:

public interface IMethodFinder{
  public Method findMethod(...);
}

然后你可以创建你的类的实例,将它们放入一个集合并循环它:

...
Method candidateMethod;
findLoop:
for (IMethodFinder mf: myMethodFinders){
  candidateMethod = mf.findMethod(clazz);
  if (candidateMethod!=null){
    break findLoop;
  }
}

if (candidateMethod!=null){
  // method found
} else {
  // not found :-(
}

虽然可能有点复杂,但如果你这样做会更容易处理需要在调用findMethods *方法之间做更多工作(例如更多验证方法是否合适),或者在运行时是否可以配置查找方法的方法列表......

尽管如此,你的方法也可以。

答案 3 :(得分:2)

我很遗憾地说,但你使用的方法似乎是广为接受的方法。我在Spring,Maven等大型库的代码库中看到了很多这样的代码。

然而,另一种方法是引入一个可以从给定输入转换为给定输出的辅助接口。像这样:

public interface Converter<I, O> {
    boolean canConvert(I input);
    O convert(I input);
}

和辅助方法

public static <I, O> O getDataFromConverters(
    final I input,
    final Converter<I, O>... converters
){
    O result = null;
    for(final Converter<I, O> converter : converters){
        if(converter.canConvert(input)){
            result = converter.convert(input);
            break;
        }

    }
    return result;
}

那么你可以编写实现逻辑的可重用转换器。每个转换器都必须实现canConvert(input)方法来决定是否使用它的转换例程。

实际上:您的请求提醒我的是Prototype(Javascript)中的Try.these(a,b,c)方法。


您案例的使用示例:

假设您有一些具有验证方法的bean。有几种策略可以找到这些验证方法。首先,我们将检查该注释是否出现在类型上:

// retention, target etc. stripped
public @interface ValidationMethod {
    String value();
}

然后我们将检查是否存在名为“validate”的方法。为了简化操作,我假设所有方法都定义了Object类型的单个参数。您可以选择不同的模式。无论如何,这是示例代码:

// converter using the annotation
public static final class ValidationMethodAnnotationConverter implements
    Converter<Class<?>, Method>{

    @Override
    public boolean canConvert(final Class<?> input){
        return input.isAnnotationPresent(ValidationMethod.class);
    }

    @Override
    public Method convert(final Class<?> input){
        final String methodName =
            input.getAnnotation(ValidationMethod.class).value();
        try{
            return input.getDeclaredMethod(methodName, Object.class);
        } catch(final Exception e){
            throw new IllegalStateException(e);
        }
    }
}

// converter using the method name convention
public static class MethodNameConventionConverter implements
    Converter<Class<?>, Method>{

    private static final String METHOD_NAME = "validate";

    @Override
    public boolean canConvert(final Class<?> input){
        return findMethod(input) != null;
    }

    private Method findMethod(final Class<?> input){
        try{
            return input.getDeclaredMethod(METHOD_NAME, Object.class);
        } catch(final SecurityException e){
            throw new IllegalStateException(e);
        } catch(final NoSuchMethodException e){
            return null;
        }
    }

    @Override
    public Method convert(final Class<?> input){
        return findMethod(input);
    }

}

// find the validation method on a class using the two above converters
public static Method findValidationMethod(final Class<?> beanClass){

    return getDataFromConverters(beanClass,

        new ValidationMethodAnnotationConverter(),
        new MethodNameConventionConverter()

    );

}

// example bean class with validation method found by annotation
@ValidationMethod("doValidate")
public class BeanA{

    public void doValidate(final Object input){
    }

}

// example bean class with validation method found by convention
public class BeanB{

    public void validate(final Object input){
    }

}

答案 4 :(得分:1)

  

...我可以将其切换为try / catch逻辑,但这几乎不会使它更具可读性。

更改get ...方法的签名以便使用try / catch这将是一个非常糟糕的主意。例外是昂贵的,只应用于“特殊”条件。正如你所说,代码的可读性会降低。

答案 5 :(得分:1)

您可以使用Decorator Design Pattern来找到找到某些内容的不同方法。

public interface FindMethod
{
  public Method get(Class clazz);
}

public class FindMethodByAnnotation implements FindMethod
{
  private final FindMethod findMethod;

  public FindMethodByAnnotation(FindMethod findMethod)
  {
    this.findMethod = findMethod;
  }

  private Method findByAnnotation(Class clazz)
  {
    return getMethodByAnnotation(clazz);
  }

  public Method get(Class clazz)
  {
    Method r = null == findMethod ? null : findMethod.get(clazz);
    return r == null ? findByAnnotation(clazz) : r;
  } 
}

public class FindMethodByOnlyMethod implements FindMethod
{
  private final FindMethod findMethod;

  public FindMethodByOnlyMethod(FindMethod findMethod)
  {
    this.findMethod = findMethod;
  }

  private Method findByOnlyMethod(Class clazz)
  {
    return getMethodOnlyMethod(clazz);
  }

  public Method get(Class clazz)
  {
    Method r = null == findMethod ? null : findMethod.get(clazz);
    return r == null ? findByOnlyMethod(clazz) : r;
  } 
}

用法非常简单

FindMethod finder = new FindMethodByOnlyMethod(new FindMethodByAnnotation(null));
finder.get(clazz);

答案 6 :(得分:0)

困扰你的是用于流控制的重复模式 - 它应该打扰你 - 但是在Java中没有太多的事情要做。

我对重复的代码&amp;像这样的模式,所以对我来说,提取重复的副本可能是值得的。粘贴控制代码并将其放入自己的方法中:

public Method findMethod(Class clazz)
    int i=0;
    Method candidateMethod = null;

    while(candidateMethod == null) {
        switch(i++) {
            case 0:
                candidateMethod = getMethodByAnnotation(clazz);
                break;
            case 1:
                candidateMethod = getMethodByBeingOnlyMethod(clazz);
                break;
            case 2:
                candidateMethod = getMethodByBeingOnlySuitableMethod(clazz);
                break;
            default:
                throw new NoSuitableMethodFoundException(clazz);
    }
    return clazz;
}

其缺点是非常规且可能更冗长,但由于“肉”中的杂乱程度较低,因此没有那么多重复代码(较少错别字)和读取的优点。

此外,一旦逻辑被提取到它自己的类中,详细信息根本不重要,它的读取/编辑清晰,对我而言,这给了(一旦你理解了while循环正在做什么)

我确实有这种令人讨厌的愿望:

case 0:    candidateMethod = getMethodByAnnotation(clazz);                break;
case 1:    candidateMethod = getMethodByBeingOnlyMethod(clazz);           break;
case 2:    candidateMethod = getMethodByBeingOnlySuitableMethod(clazz);   break;
default:   throw new NoSuitableMethodFoundException(clazz);

要突出显示实际上正在进行的操作(按顺序),但在Java中这是完全不可接受的 - 您实际上会发现它在其他语言中是常​​见的或首选的。

PS。在groovy中,这将是彻头彻尾的优雅(该死的我讨厌那个词):

actualMethod = getMethodByAnnotation(clazz)                   ?:
               getMethodByBeingOnlyMethod(clazz)              ?:
               getMethodByBeingOnlySuitableMethod(clazz)      ?:
               throw new NoSuitableMethodFoundException(clazz) ;

elvis运营商规则。注意,最后一行可能实际上不起作用,但如果没有,那将是一个微不足道的补丁。