Pull to refresh

Плотный код и его тестирование

Reading time 3 min
Views 1.4K
Данная статья написана по следам статьи Steve Yegge. A portrait of a n00b (с подачи хабраперевода). Для меня основной посыл статьи прозвучал так — меньше комментариев, меньше классов, меньше методов, больше кода. Делайте код плотнее, не усердствуйте в моделировании. Мне сложно судить это видение мира, да и вообще вопрос комментариев как таковых больше умозрительный. Но вот что засело во мне стальной иглой, так это проблема тестирования такого плотного кода.



Чтобы обрисовать проблему, давайте рассмотрим небольшой пример. Допустим, у вас есть сервер и ему извне поступают команды. Вам необходимо написать логику обработки этих команд. Я бы смоделировал эту проблему как-то так:



Почему именно так и столько: во-первых, мы придерживаемся принципа Code to Interface, что позволит нам, например, при тестировании сервера подделать (mock) обработчик команд; во-вторых, изолируя логику каждой команды в отдельном классе, мы можем легко протестировать эту логику. Вы можете подумать, почему бы нам просто не сделать один класс Handler и в него добавить публичные методы для каждой команды и подвергать тестам эти методы, зачем городить кучу классов? Но, на мой взгляд, проблема в том, что функция класса в этом случае как-бы «размазывается» по классу, мы в каком-то смысле открываем внешнему миру ненужные ему подробности реализации нашего обработчика. Ведь всё, что сервер должен знать, это то, что данный класс может обработать любую команду, а не то, что он может обработать команду 1, команду 2 и команду 3.

Теперь попробуем подумать, как бы эта проблема была реализована в виде «плотного кода». Конечно, я не могу говорить и думать за автора, человека с 20-25 годами опыта. Возможно, он имеет в виду нечто другое. Но в данном контексте для меня уплотнение кода — это, например, спайка всех обработчиков в один класс. А ещё лучше в один метод:

public class Handler {
    public Result handle(Command command) {
        if (command == COMMAND1) {
            // perform logic 1
        } else if (command == COMMAND2) {
            // perform logic 2
        } 
        // . . . .
        else {
            log.error("Don't know how to handle command " + command);
        }
        return result;
    }
}


А может быть, вообще не выносить в отдельный класс обработку команд? Просто поместить их прямо в сервер?

public class Server {
    public void converse(Socket client) {
        int command;
        while ((command = client.getInputStream().read()) != -1) {
            if (command == COMMAND1) {
                // perform logic 1
            } else if (command == COMMAND2) {
                // perform logic 2
            } 
            // . . . .
            else {
                log.error("Don't know how to handle command " + command);
            }
        }
    }
}


Как вы предполагаете это тестировать? Тут только интеграционные тесты могут хоть как-то спасти отца русской демократии. Возможно, автор имел в виду нечто совсем другое? Буду рад, если хабрагуру просветят меня и других «подростков» на эту тему.

В мультипарадигменных языках типа Lisp или Python ситуация несколько иная. Функции в них имеют тенденцию превращаться в многоуровневые наслоения map-filter-reduce.
Здесь, мне кажется, нет особого смысла выделять каждую операцию map, filter или reduce в отдельную функцию. Достаточно использовать хорошо протестированные предикаты вместо невменяемых лямбд, комментировать самые важные слои map-filter-reduce, а также хорошо тестировать конечные функции.

P.S. Оговорюсь, что я вполне допускаю плотную реализацию в императивных языках некоторых алгоритмов наподобие сортировки. Такие алгоритмы как жонглёры — нет никакого смысла жонглировать одним шаром, смысл появляется только при одновременном жонглировании множеством шаров (объектов, переменных и т.п.). Хотя тут тоже, мне кажется, бывают варианты.
Tags:
Hubs:
+2
Comments 8
Comments Comments 8

Articles