Предыдущая глава: Значимые имена

Первое правило функций состоит в том, что они должны быть маленькими. Второе правило функций состоит в том, что они должны быть меньше этого размера. Функции не должны состоять из 100 строк и даже почти никогда не должны состоять из 20 строк.

См. пример кода ниже. Он длинный и включает повторяющийся код, множество нечетных строк и множество странных типов данных:

public static String testableHtml(
            PageData pageData,
            boolean includeSuiteSetup
    ) throws Exception {
        WikiPage wikiPage = pageData.getWikiPage();
        StringBuffer buffer = new StringBuffer();
        if (pageData.hasAttribute("Test")) {
            if (includeSuiteSetup) {
                WikiPage suiteSetup =
                        PageCrawlerImpl.getInheritedPage(
                                SuiteResponder.SUITE_SETUP_NAME, wikiPage
                        );
                if (suiteSetup != null) {
                    WikiPagePath pagePath =
                            suiteSetup.getPageCrawler().getFullPath(suiteSetup);
                    String pagePathName = PathParser.render(pagePath);
                    buffer.append("!include -setup .")
                            .append(pagePathName)
                            .append("\n");
                }
            }
            WikiPage setup =
                    PageCrawlerImpl.getInheritedPage("SetUp", wikiPage);
            if (setup != null) {
                WikiPagePath setupPath =
                        wikiPage.getPageCrawler().getFullPath(setup);
                String setupPathName = PathParser.render(setupPath);
                buffer.append("!include -setup .")
                        .append(setupPathName)
                        .append("\n");
            }
        }
        buffer.append(pageData.getContent());
        if (pageData.hasAttribute("Test")) {
            WikiPage teardown =
                    PageCrawlerImpl.getInheritedPage("TearDown", wikiPage);
            if (teardown != null) {
                WikiPagePath tearDownPath =
                        wikiPage.getPageCrawler().getFullPath(teardown);
                String tearDownPathName = PathParser.render(tearDownPath);
                buffer.append("\n")
                        .append("!include -teardown .")
                        .append(tearDownPathName)
                        .append("\n");
            }
            if (includeSuiteSetup) {
                WikiPage suiteTeardown =
                        PageCrawlerImpl.getInheritedPage(
                                SuiteResponder.SUITE_TEARDOWN_NAME,
                                wikiPage
                        );
                if (suiteTeardown != null) {
                    WikiPagePath pagePath =
                            suiteTeardown.getPageCrawler().getFullPath (suiteTeardown);
                    String pagePathName = PathParser.render(pagePath);
                    buffer.append("!include -teardown .")
                            .append(pagePathName)
                            .append("\n");
                }
            }
        }
        pageData.setContent(buffer.toString());
        return pageData.getHtml();
    }

Вы понимаете функцию за три минуты? Там слишком много всего происходит на слишком многих разных уровнях абстракции.

Код превращается в это с помощью небольшого рефакторинга:

public static String renderPageWithSetupsAndTeardowns(
            PageData pageData, boolean isSuite
    ) throws Exception {
        boolean isTestPage = pageData.hasAttribute("Test");
        if (isTestPage) {
            WikiPage testPage = pageData.getWikiPage();
            StringBuffer newPageContent = new StringBuffer();
            includeSetupPages(testPage, newPageContent, isSuite);
            newPageContent.append(pageData.getContent());
            includeTeardownPages(testPage, newPageContent, isSuite);
            pageData.setContent(newPageContent.toString());
        }
        return pageData.getHtml();
    }

Это более читабельно и не долго. Но каково второе правило? Он должен быть меньше этого.

public static String renderPageWithSetupsAndTeardowns(
            PageData pageData, boolean isSuite) throws Exception {
        if (isTestPage(pageData))
            includeSetupAndTeardownPages(pageData, isSuite);
        return pageData.getHtml();
    }

Первый пример кода делает больше, чем одну вещь. Создание буферов, выборка страниц, поиск унаследованных страниц, пути рендеринга и т. д. Но последний пример кода делает одну вещь. Это включает в себя настройки и разборки на тестовых страницах.

ФУНКЦИИ ДОЛЖНЫ ДЕЛАТЬ ОДНУ ВЕЩЬ. ОНИ ДОЛЖНЫ СДЕЛАТЬ ЭТО ХОРОШО. ТОЛЬКО ОНИ ДОЛЖНЫ ЭТО СДЕЛАТЬ.

Основная проблема заключается в том, что трудно понять, что такое «одна вещь». Последний код делает что-то одно? Он делает три вещи: определяет, является ли страница тестовой, если да, включая настройки и демонтаж, визуализацию страницы в HTML.

Если функция выполняет только шаги, указанные в ее имени, эта функция выполняет одно действие.

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

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

Не стесняйтесь делать имя длинным. Длинное описательное имя лучше, чем короткое бессмысленное имя. Длинное описательное имя лучше, чем длинные описательные комментарии.

Не бойтесь тратить время на выбор имени. Вы должны попробовать несколько разных имен и прочитать каждую часть кода.

Будьте последовательны в своих именах. Используйте те же фразы, существительные и глаголы в именах функций, которые вы выбираете для своих модулей.

Какое идеальное количество аргументов у функции? Нулевой аргумент в порядке. Один-два аргумента допустимы. Но избегайте трех аргументов. И в любом случае не следует использовать более трех.

Если вы указываете один аргумент для функции, вероятно, у вас есть две причины: во-первых, вы можете задать вопрос этому аргументу, как в boolean fileExists("MyFile").
второй, вы можете преобразовать его во что-то другое и вернуть. Например, InputStream fileOpen("MyFile") преобразует имя файла String в возвращаемое значение InputStream.
Как только кто-то прочитает эти два употребления, он поймет, что это такое.

Другой (менее) распространенный способ использования одного аргумента для функции — это событие . В этой форме есть входной аргумент, но нет выходного аргумента. Программа интерпретирует вызов этой функции как событие и использует аргумент для изменения состояния системы. Например, void passwordAttemptFailedNtimes(целое число попыток). Читателю должно быть совершенно ясно, что это событие. Тщательно выбирайте имена и контексты.

Когда вы преобразуете его входной аргумент, вы должны использовать выходной аргумент. Например, преобразование StringBuffer(StringBuffer in)лучше, чем void includeSetupPageInto(StringBuffer pageText), даже если возвращается StringBuffer . По крайней мере, появляется возвращаемое значение.

Аргументы флага уродливы. Передача логического значения в функцию — ужасная практика. Это означает, что эта функция делает несколько вещей. Он делает одно, если флаг истинный, и другое, если флаг ложный.

Если функции требуется более двух или трех аргументов, эти аргументы должны быть заключены в класс. Например:
Окружность makeCircle(двойной x, двойной y, двойной радиус);
Окружность makeCircle(точка центра, двойной радиус);

Создание объектов кажется обманом, но это не так.

Побочные эффекты - ложь. Ваша функция говорит, что она будет делать одну вещь. Но он также делает и другие скрытые вещи.

См. пример кода ниже:

public class UserValidator {
    private Cryptographer cryptographer;
    public boolean checkPassword(String userName, String password) {
        User user = UserGateway.findByName(userName);
        if (user != User.NULL) {
            String codedPhrase = user.getPhraseEncodedByPassword();
            String phrase = cryptographer.decrypt(codedPhrase, password);
            if ("Valid Password".equals(phrase)) {
                Session.initialize();
                return true;
            }
        }
    return false;
    }
}

Имя функции — checkPassword. Читатель просто ожидает, что он проверит пароль. Но эта функция также выполняет Session.initialize(). Поэтому вызов этой функции может привести к удалению данных текущего сеанса. Имя функции должно быть checkPasswordAndInitializeSession.

Блоки Try/Catch уродливы. Они усложняют код и смешивают обработку ошибок и обычную обработку. Они должны извлекать тела блоков try и catch в собственные функции.

public void delete(Page page) {
    try {
        deletePageAndAllReferences(page);
    }
    catch (Exception e) {
        logError(e);
    }
 }
 
private void deletePageAndAllReferences(Page page) throws Exception {
    deletePage(page);
    registry.deleteReference(page.name);
    configKeys.deleteKey(page.name.makeKey());
}

private void logError(Exception e) {
    logger.log(e.getMessage());
}

Следующая глава: Комментарии