改进 if-else 语句的圈复杂度代码

Improve the cyclomatic complexity code for if-else statements

我在遇到圈复杂度问题的方法中有一个 if-else 语句块。我已经尝试为此使用 switch 语句,但问题仍然存在。

if (fileName.startsWith(HwErrorsEtlConstants.MR_RAW_GRADIENT_ERRORS)) {
    method1()...
} else if (fileName.startsWith(HwErrorsEtlConstants.MR_RAW_PFEI_ERRORS)) {
    method2()...
} else if (fileName.startsWith(HwErrorsEtlConstants.MR_RAW_RFAMP_ERRORS)) {
    method3()...
} and so on...

public static void method1(IOutputWriter writer, String fileName, InputStream fileInputStream) throws IOException {
        //logic for the method
    }

您可以通过 Java 8 使用谓词和函数功能接口的方法解决此问题。你必须把所有的条件都通过谓词和函数,你可以添加条件匹配时需要执行的实现。

Map<Predicate,Function<String,String>> map = new HashMap<>();

    Predicate<String> p = (fileName)->fileName.startsWith(HwErrorsEtlConstants.MR_RAW_GRADIENT_ERRORS);

  Function<String,String> method = (input)->{ return "output";}; 

   map.put(p,method);
    Optional<String> result =
    map.entrySet()
    .stream()
    .filter(entry->entry.getKey().test(fileName))
    .map(entry->entry.getValue().apply())
    .findFirst();

  map.entrySet()
    .stream()
    .filter(entry->entry.getKey().test(fileName))
    .forEach(entry->entry.getValue().apply());

Edit:正如您提到的,您已经检查了可以在您的方法中抛出 参数的 Exceptions。由于 Runnable 没有声明它可以抛出 Exceptions 并且也不接受您必须创建自己的 FunctionalInterface 的任何参数(单击 以查看它们真的是):

public interface ThrowingRunnable {
    void run(IOutputWriter writer, String fileName, InputStream fileInputStream) throws IOException;
}

那么你只需要在我之前建议的下面的代码中用 ThrowingRunnable 替换 Runnable 就可以了。


您可以创建 HwErrorsEtlConstants 到特定方法的映射(使用 java 8):

static final Map<String, Runnable> MAPPING;
static {
    Map<String, Runnable> temp = new HashMap<>();
    temp.put(HwErrorsEtlConstants.MR_RAW_GRADIENT_ERRORS, this::method1);
    temp.put(HwErrorsEtlConstants.MR_RAW_PFEI_ERRORS, this::method2);
    temp.put(HwErrorsEtlConstants.MR_RAW_RFAMP_ERRORS, this::method3);
    MAPPING = Collections.unmodifiableMap(temp);
}

然后在你的方法中你可以使用 Streams 在 Java 8:

中也有介绍
// Optional indicates a potentially absent value, it's just a wrapper around a Runnable
Optional<Runnable> optional = MAPPING
    // create a Stream from the entries
    .entrySet().stream()
    // keep the items that match the condition and drop every other
    .filter(e -> filename.startsWith(e.getKey()))
    // we had Map.Entry<String, Runnable>, but now we only need the value e.g. the Runnable
    .map(Map.Entry::getValue)
    // short circuit, e.g. we only want the first value that matches
    .findFirst();

// checks if anything is present, this is used as the MAPPING "could" be empty
if(optional.isPresent()) {
    // unpack the value and call it with arguments
    optional.get().run(aWriter, someFileName, anInputStream);
} else {
    // nothing matched, throw error or log etc.
}

尽管如前所述,您当前的解决方案看起来不错,但我猜您正在使用 Sonar 进行代码分析。有时声纳只是有误报,所以你也可以安全地忽略它们。

进一步阅读以帮助您理解 Java 8:

cyclomatic complexity 是一个问题:确实 周期

然后使用 if/switch 在 属性 上拆分流,这不是 object-oriented。它还可能违反关注点分离:有许多方法处理完全不同的较小方面。

如果字段数较多,行数较多,考虑提取类处理一个方面

为了降低圈复杂度,检查控制调用者的流程,相同调用的重复,实用的重复代码。然后尝试(重新)移动周期。 最好是可以将循环放在一起;并致力于 Lists/Sets/Streams.