Есть много книг о том, как писать хороший программный код на Java, например. Блох (2008 г.): Эффективная Java или Мартин (2009 г.): Чистый код. Однако гораздо меньше внимания в литературе уделяется искусству ухудшения качества исходного кода. Тем не менее, любой может научиться этому навыку с небольшой практикой. На самом деле обычно требуется очень мало шагов, как продемонстрирует следующий пример.

Мы начнем с некоторого примера кода, который обычно рекомендуется в книгах, упомянутых выше.

public class DBWrapper {

    DB db;

    public DBWrapper(DB db){
        this.db = db;
    }

    public final List<Product> search(final String name) {
        checkNotNull(name, "Name is null!");

        final List<Product> products = db.findAll(name);

        if(products == null) {
            return Collections.emptyList();
        }
        return products;
    }

    private Object checkNotNull(final Object o, String message) {
        if(o == null) {
            throw new IllegalArgumentException(message);
        }
        return o;
    }
}

Класс DBWrapper представляет собой оболочку (также называемую декоратором) вокруг класса базы данных. Наше внимание сосредоточено на методе search(). Он находит все продукты с заданным именем. Вызов выглядит, например. так

Product p1 = new Product(“name1”); 
Product p2 = new Product(“name2”); 
Product p3 = new Product(“name3”); 
dbWrapper = new DBWrapper(new DB(p1,p2,p3)); 
List<Product> results = dbWrapper.search(“name3”); 
assertTrue(results.get(0).getName().equals(“name3”));

Метод имеет некоторые неприятные свойства. Например, он выдает IllegalArgumentException при передаче значения null и никогда не возвращает nullсам по себе. Это идеальный кандидат для изучения искусства деградации исходного кода (также называемого рефакторингом).

Мы начинаем с

1. Принятие null

public class DBWrapper {

    DB db;

    public DBWrapper(DB db){
        this.db = db;
    }

    public final List<Product> search(final String name) {
        final List<Product> products = db.findAll(name);

        if(products == null) {
            return Collections.emptyList();
        }
        return products;
    }
}

Как видим, удалив лишний nullcheck, мы уже сэкономили значительное количество строк кода. Это было хорошее начало, теперь наш метод будет выдавать разумное значение NullPointerException при передаче значения null.

2. Возврат нуля

public class DBWrapper {

    DB db;

    public DBWrapper(DB db){
        this.db = db;
    }

    public final List<Product> search(final String name) {
        return db.findAll(name);
    }
}

Метод db.findAll() (как и многие неправильно реализованные API-интерфейсы базы данных) возвращает null, если не удается найти ни одного совпадения при поиске. Наш DBWrapper тоже не должен пытаться это улучшить. Делая это, мы можем снова сэкономить много строк, и теперь мы уже сократили код метода до одной строки. На этом этапе новичок уже может быть доволен результатом, но мы только начинаем.

3. Удаление финала

public class DBWrapper {

    DB db;

    public DBWrapper(DB db){
        this.db = db;
    }

    public List<Product> search(String name) {
        return db.findAll(name);
    }
}

Каждый современный разработчик работает по Agile. В такой среде ключевое слово final просто раздражает, так как предотвращает перезапись метода в производном классе для методов и сброс значения для переменных. Это отсутствие гибкости подрывает процесс гибкой разработки и поэтому должно считаться устаревшим.

4. Удаление дженериков

public class DBWrapper {

    DB db;

    public DBWrapper(DB db){
        this.db = db;
    }

    public List search(String name) {
        return db.findAll(name);
    }
}

Как известно каждому разработчику, дженерики в любом случае удаляются компилятором. Так зачем хранить их в исходном коде? Без дженериков наш код стал еще короче и гибче. Теперь наш список может содержать не только объекты класса Product, но и, например. класса Person или просто строки.

5. Использование базового класса Object

public class DBWrapper {

    DB db;

    public DBWrapper(DB db){
        this.db = db;
    }

    public List search(Object name) {
        return db.findAll((String) name);
    }
}

Для гибкого разработчика безопасность типов в языке программирования — только помеха. Но Java предлагает выход: базовый класс Object, от которого происходят все остальные классы. Чтобы сделать наш метод search() максимально гибким, имеет смысл использовать в качестве параметра метода Object вместо String. В нашем случае мы должны ввести приведение до вызова findAll(), но явные приведения являются обязательными для успешного рефакторинга.

6. Внедрение зависимостей в метод

public class DBWrapper {
 
 public static List search(Object s, DB db) {
     return db.findAll((String) name);
 }
}

Внедрение зависимостей через конструктор знакомо всем. Но ясность этого подхода не способствует достижению наших целей. А так как у нашего метода и так слишком мало параметров, то имеет смысл передать зависимость от базы данных напрямую в метод. Кроме того, мы сохраняем переменную класса db и конструктор, потому что мы также можем сделать метод статическим. Бог производительности возлюбит нас за это.

7. Возврат значений путем изменения параметров метода и использования void

public class DBWrapper {

 public static void search(Object s, DB db, List results) {
  results.addAll(db.findAll((String) s));
 }
}

И наконец, мы воспользуемся преимуществом того факта, что в Java при передаче объекта методу копируется указатель на этот объект (см. http://javadude.com/articles/passbyvalue.htm). Это позволяет нам передавать объект в качестве параметра метода и изменять этот объект, используя копию указателя внутри метода. После этого возвращаемые значения больше не нужны, и мы можем объявить наш метод как метод void. Методы с неожиданными побочными эффектами — вот что отличает профессионального специалиста по ухудшению кода от новичка. Если вы хотите быть абсолютно уверенным, вы можете скрыть объект, который изменяется в методе, где-то в середине списка параметров. И помните: профессионал оставляет все существующие комментарии нетронутыми при изменении порядка параметров. Не то, чтобы кто-то другой использовал этот метод правильно с первого раза.

После наших 7 простых шагов мы достигли нашей цели. Наш код намного короче из-за рефакторинга. Но из-за этого вызов метода стал гораздо более неясным.

Product p1 = new Product("name1");
Product p2 = new Product("name2");
Product p3 = new Product("name3");
 
List results = new ArrayList<>();
DBUtil.search("name3", new DB(p1,p2,p3), results);

assertTrue(((Product) results.get(0)).getName().equals("name3"));

Кроме того, наш метод теперь принимает произвольные объекты при компиляции, а также null в качестве параметра запроса, что должно быть полезно для некоторых неожиданных RuntimeExceptions. Если поиск не дает результатов, метод вылетает с красивым NullPointerException (благодаря вызову List.addAll(null)).

Мы видим, что искусство деградации или рефакторинга кода не так уж сложно, если только вы осмелитесь это сделать. Мы надеемся, что смогли дать заинтересованному читателю хорошие основы для начала работы в этой увлекательной области программирования, и надеемся, что вам понравится успешно применять наши семь советов на практике.

PS Если вам нужно еще больше вдохновения, ознакомьтесь со ссылкой на эту тему.