Prefer self documenting code over cyclomatic complexity?

I’m interested in which approach to prefer. Consider some code which retrieves a translation for some text. It has to respect these constraints:

  • Return translation only if the text will be used on a UI. If it’s used some where else (debug log), return the normalized version of the text.
  • Text normalization is costly (cpu resources). We should do it only when it is required.
  • Loading the translation is costly as well.
  • If translation for the text is missing or empty, return normalized original text as well.

First approach:

boolean useFallback = true;
if (textUsedForUi()) {
    final String translation = getNonEmptyTranslation(text);
    if (translation != null) {
        result.setText(translation);
        useFallback = false;
    }
}
if (useFallback) {
    result.setText(normalize(text));
}

String getNonEmptyTranslation(String text) {
    String translation = loadTranslation(text);
    if (StringUtils.isBlank(translation)) {
        return null;
    } else {
        return translation;
    }
}

Second approach:

String translation = getTranslation(text);
if (translation != null) {
    result.setText(translation);
} else {
    result.setText(normalize(text));
}

String getTranslation(String text) {
    if (!textUsedForUi()) {return null;}
    String translation = loadTranslation(text);
    if (StringUtils.isBlank(translation)) {
        return null;
    } else {
        return translation;
    }
}

First approach clearly documents that the translation depends on where the text will be used and if a translation is available. But I have to introduce a helper variable to avoid code duplication and not recommendable assignments in conditions:

String translation;
if (textUsedForUi() && (translation = getNonEmptyTranslation(text)) != null)

I don’t want to rely on the previous value of result.getText(), so I can’t use this as a flag whether to use the fallback or not.

The second approach is clearly simpler (reduced cyclomatic complexity). But it hides the dependency to where the text is used in a sub routine. This reduces the self documenting nature of the code.

Which approach to prefer?

5

I think the answer depends on whether getNonEmptyTranslation or getTranslation would be more generally reusable, outside of this particular piece of code. If neither are used outside of this routine, I would consider inlining the function. Apart from that you can replace the useFallback variable with a return:

if (textUsedForUi()) {
    final String translation = getNonEmptyTranslation(text);
    if (translation != null && StringUtils.isBlank(translation)) {
        result.setText(translation);
        return;
    }
}

result.setText(normalize(text));

1

The second version is worse because the getTranslation() method does not necessarily do what its name indicates, but switches its behavior due to a totally unrelated factor. Confusing and misleading code is much worse than a few points of CC.

The “nonEmpty” part of getNonEmptyTranslation() is pointless. One could check for empty just as easy as for null. Nor do you need a fallback variable. So, based on that, I’d suggest

String translation = "";

if (textUsedForUi()) {
  translation = getTranslation(text);  // return empty if none

if (transation.length() > 0)
   result.setText(translation);
else
   result.setText(normalize(text));

One could shorten the code even more with tertiary operators (? 🙂 but I think it’s clearer without them.

2

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *