Выбрать главу

      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();

}

Удалось ли вам разобраться с функцией за три минуты? Вероятно, нет. В ней происходит слишком много всего, и притом на разных уровнях абстракции. Загадочные строки и непонятные вызовы функций смешиваются в конструкциях if двойной вложенности, к тому же зависящих от состояния флагов.

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

Листинг 3.2. HtmlUtil.java (переработанная версия)

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();

}

Если только вы не занимаетесь активным изучением FitNesse, скорее всего, вы не разберетесь во всех подробностях. Но по крайней мере вы поймете, что функция включает в тестовую страницу какие-то начальные и конечные блоки, а потом генерирует код HTML. Если вы знакомы с JUnit[11], то, скорее всего, поймете, что эта функция является частью тестовой инфраструктуры на базе Web. И конечно, это правильное предположение. Прийти к такому выводу на основании листинга 3.2 несложно, но из листинга 3.1 это, мягко говоря, неочевидно.

Что же делает функцию из листинга 3.2 такой понятной и удобочитаемой? Как заставить функцию передавать намерения разработчика? Какие атрибуты функции помогут случайному читателю составить интуитивное представление о выполняемых ей задачах?

Компактность!

Первое правило: функции должны быть компактными. Второе правило: функции должны быть еще компактнее. Я не могу научно обосновать свое утверждение. Не ждите от меня ссылок на исследования, доказывающие, что очень маленькие функции лучше больших. Я могу всего лишь сказать, что я почти четыре десятилетия писал функции всевозможных размеров. Мне доводилось создавать кошмарных монстров в 3000 строк. Я написал бесчисленное множество функций длиной от 100 до 300 строк. И я писал функции от 20 до 30 строк. Мой практический опыт научил меня (ценой многих проб и ошибок), что функции должны быть очень маленькими. В 80-е годы считалось, что функция должна занимать не более одного экрана. Конечно, тогда экраны VT100 состояли из 24 строк и 80 столбцов, а редакторы использовали 4 строки для административных целей. В наши дни с мелким шрифтом на хорошем большом мониторе можно разместить 150 символов в строке и 100 и более строк на экране. Однако строки не должны состоять из 150 символов, а функции — из 100 строк. Желательно, чтобы длина функции не превышала 20 строк.

Насколько короткой может быть функция? В 1999 году я заехал к Кенту Беку в его дом в Орегоне. Мы посидели и позанимались программированием. В один момент он показал мне симпатичную маленькую программу Java/Swing, которую он назвал Sparkle. Программа создавала на экране визуальный эффект, очень похожий на эффект волшебной палочки феи-крестной из фильма «Золушка». При перемещении мыши с курсора рассыпались замечательные блестящие искорки, которые осыпались к нижнему краю экрана под воздействием имитируемого гравитационного поля. Когда Кент показал мне код, меня поразило, насколько компактными были все функции. Многие из моих функций в программах Swing растягивались по вертикали чуть ли не на километры. Однако каждая функция в программе Кента занимала всего две, три или четыре строки. Все функции были предельно очевидными. Каждая функция излагала свою историю, и каждая история естественным образом подводила вас к началу следующей истории. Вот какими короткими должны быть функции[12]!

вернуться

11

Программа модульного тестирования для Java, распространяемая с открытым кодом — www.junit.org.

вернуться

12

Я спросил Кента, не сохранилась ли у него эта программа, но ему не удалось ее найти. Обшарил все свои старые компьютеры — тоже безуспешно. Остались лишь мои воспоминания об этой программе.