Статьи

Как технический долг OpenKM снизился на 49% благодаря рефакторингу кода

Технический долг ничего не стоит, если в коде не предпринимаются прагматические действия для его контроля и решения. Для ilustrate  Scertifyспособности s автоматически корректировать дефекты кода , которые увеличивают этот непреднамеренный долг, мы выполнили код рефакторинга OpenKM, система управления документами Free / Libre.

Первоначальный технический долг проекта был уменьшен на 49,2% с 84 до 42 дней. Здесь, в Точе, мы называем это списанием долгов.

Для этого первого списания долга мы решили выполнить рефакторинг  OpenKM  (6.2.1-DEV).

Согласно  Википедии , OpenKM — это система управления документами Free / Libre, которая предоставляет веб-интерфейс для управления произвольными файлами. OpenKM — отличный инструмент, но проверка кода выявила некоторые технические проблемы с долгами. Это была хорошая возможность использовать Scertify и быть полезной для сообщества открытого кода. Приложение состоит в основном из 200 тысяч строк кода Java. Существует также Javascript, JSP, CSS … но мы сосредоточимся здесь на коде Java.

Технический долг перед рефакторингом

Scertify Refactoring Assessment allows us to estimate the technical debt of the application. As you can see on screenshot #1, it is estimated to 84 days. This is the time needed to correct manually each error. This number only includes the time needed to make the change on the code, it does not include things like finding the file, understanding the problem, etc.

Of this 84 days, 60 represent errors that can be automatically refactored, thus taking nearly zero effort to correct.

We can take a closer look on the possibilities of automation (screenshot #2). Not all rules are currently implemented in Scertify, but we are working on it. For this project, we chose 7 rules that seemed particularly interesting.

Rules used for the refactoring

Here’s a presentation of the rules used to perform the refactoring of OpenKM.

  • AvoidPrintStackTrace

    This rule reports a violation when it finds a code that catch an expression and print its stack trace to the standard error output. A logging framework should be used instead, in order to improve application’s maintainability.
    The refactoring replace a call to print stack trace by a call to a logging framework. The rule can also declare the logger in the class and make the required imports. The rule can be configured to use the user’s favorite framework.

    Here’s the configuration used for OpenKM:

    • The logger call to use: “log.error({0}.getMessage(), {0})” {0} is replaced by the exception.
    • Do not refactor calls of printStackTrace to other IO (a file, a stream…)
    • Make logger declaration when it’s needed (ie: log is not already declared in class).
    • The logger declaration to use : “private static Logger log = LoggerFactory.getLogger({0}.class);”
    • The required imports : “org.slf4j.LoggerFactory,org.slf4j.Logger”


    Original code :

    catch(FileNotFoundExceptione){ e.printStackTrace(); }
  • Refactored code :

    catch(FileNotFoundExceptione){ log.error(e.getMessage(),e); }

  • AddEmptyStringToConvert

    Using the concatenation of an empty string to convert a primitive type to a String is a bad practice. First of all, it makes the code less readable. It is also less efficient in most cases (the only case where the string concatenation is slightly better is when the primitive is final).

    Original code:

    UserActivity.log(session.getUserID(),"DELETE_PROCESS_DEFINITION",""+processDefinitionId,null,null);

    Refactored code:

    UserActivity.log(session.getUserID(),"DELETE_PROCESS_DEFINITION",String.valueOf(processDefinitionId),null,null);

  • InefficientConstructorCall

    Calling the constructor of a wrapper type, like Integer, to convert a primitive type is a bad practice. It is less efficient than calling the static method valueOf.

    Original code:

    users.put(usersRead[i].getString(),newInteger(Permission.READ));

    Refactored code:

    users.put(usersRead[i].getString(),newInteger(Permission.READ));

  • IfElseStmtsMustUseBraces

    This rule finds if statements that don’t use braces. The refactoring adds required braces.

  • PositionLiteralsFirstInComparisonsRefactor

    This rule checks that literals are in the first position in comparisons. The following code is a violation :

    Original code:

    if(action.equals("ruleList")) 

    Refactored code:

    if("ruleList".equals(action))

    The refactoring invert the literal and the variable. This ensures that the code cannot crash due to the variable being a null pointer.

  • MethodArgumentCouldBeFinal

    This method flags method’s arguments that could be declared final and are not. The use of the final keyword is a useful information for future code readers.

  • LocalVariableCouldBeFinal

    The purpose is the same as the previous rule, except that it treats local variable and not arguments. These two rules are not critical, but since they have a huge number of violations, it is useful to get rid of them quickly with automatic refactoring.

We are now ready to perform the refactoring with Scertify.

The refactoring process

The refactoring process consists of 2 steps :

  1. Configure a xml rule repository: The first step is crucial. As we have seen in previous section, some rules need to be configured to be useful. However, it shouldn’t take more than half an hour.
  2. Run Scertify to perform the refactoring: The second step is just a command line invocation, where you specify the project to refactor and the rule repository to use.

For this project of 200K lines of code, the refactoring took 2 minute. You can check the process on a smaller project in this video tutorial.

Technical debt after refactoring

Screenshot #3 is the analysis of the refactored project by Scertify Refactoring Assessment. As you can see, 24 days of technical debt have been erased.

Screenshot #4 and #5 show the difference of violations in Sonar between the original and the refactored project.

Here’s the number of violations that have been corrected for each rule (*) :

  • AddEmptyStringToConvert: 232
  • AvoidPrintStackTrace: 70
  • InefficientConstructorCall: 43
  • IFElseStmtsMustUseBraces: 411
  • PositionLiteralsFirstInComparisons: 358
  • MethodArgumentCouldBeFinal: 8848
  • LocalVariableCouldBeFinal : 7622

To sum up, with Scertify we’ve been able to correct quickly a huge number of errors. Some of them are not critical (like MethodArgumentCouldBeFinal) but we’ve also been able to refactor more evolved errors like AvoidPrintStackTrace, AddEmptyStringToConvert,…

Download the source files

(*) if you do the math, you’ll see that more errors have been corrected. It’s due to side effects of the refactoring (correcting a rule can remove violations of other rules) and also because we manually corrected few things in the code.

Submit your project for a Debt Write-Off

If you’re interested in submitting your project to the next Debt Write-off, or just give some valuable feedback… Please contact us on Twitter: @Scertify