Главная страница
Навигация по странице:

  • Вертикальное форматирование

  • Вертикальное разделение концепций

  • "); html.append(childHtml()).append("

  • TestRunner tr = m_runnerFactory.newTestRunner(this, test);

  • private String fName; private Vector fTests= new Vector (10);

  • Создание, анализ ирефакторинг


    Скачать 3.16 Mb.
    НазваниеСоздание, анализ ирефакторинг
    Дата29.09.2022
    Размер3.16 Mb.
    Формат файлаpdf
    Имя файлаChistyj_kod_-_Sozdanie_analiz_i_refaktoring_(2013).pdf
    ТипКнига
    #706087
    страница13 из 49
    1   ...   9   10   11   12   13   14   15   16   ...   49
    100
    Глава 4 . Комментарии
    листинг 4 .8 . PrimeGenerator.java (переработанная версия)
    /**
    * Класс генерирует простые числа до максимального значения, заданного
    * пользователем, по алгоритму "Решета Эратосфена".
    * Берем массив целых чисел, начиная с 2, и вычеркиваем
    * из него все числа, кратные 2. Находим следующее невычеркнутое число
    * и вычеркиваем все числа, кратные ему. Повторяем до тех пор, пока из массива
    * не будут вычеркнуты все кратные.
    */
    public class PrimeGenerator
    {
    private static boolean[] crossedOut;
    private static int[] result;
    public static int[] generatePrimes(int maxValue)
    {
    if (maxValue < 2)
    return new int[0];
    else
    {
    uncrossIntegersUpTo(maxValue);
    crossOutMultiples();
    putUncrossedIntegersIntoResult();
    return result;
    }
    }
    private static void uncrossIntegersUpTo(int maxValue)
    {
    crossedOut = new boolean[maxValue + 1];
    for (int i = 2; i < crossedOut.length; i++)
    crossedOut[i] = false;
    }
    private static void crossOutMultiples()
    {
    int limit = determineIterationLimit();
    for (int i = 2; i <= limit; i++)
    if (notCrossed(i))
    crossOutMultiplesOf(i);
    }
    private static int determineIterationLimit()
    {
    // Каждое кратное в массиве имеет простой множитель, больший либо равный
    // квадратному корню из размера массива. Следовательно, вычеркивать элементы,
    // кратные числам, превышающих квадратный корень, не нужно.
    double iterationLimit = Math.sqrt(crossedOut.length);
    return (int) iterationLimit;
    }
    private static void crossOutMultiplesOf(int i)
    {
    for (int multiple = 2*i;
    100

    Литература
    101
    multiple < crossedOut.length;
    multiple += i)
    crossedOut[multiple] = true;
    }
    private static boolean notCrossed(int i)
    {
    return crossedOut[i] == false;
    }
    private static void putUncrossedIntegersIntoResult()
    {
    result = new int[numberOfUncrossedIntegers()];
    for (int j = 0, i = 2; i < crossedOut.length; i++)
    if (notCrossed(i))
    result[j++] = i;
    }
    private static int numberOfUncrossedIntegers()
    {
    int count = 0;
    for (int i = 2; i < crossedOut.length; i++)
    if (notCrossed(i))
    count++;
    return count;
    }
    }
    Можно возразить, что первый комментарий избыточен, потому что он практиче- ски полностью повторяет код самой функции generatePrimes
    . И все же я считаю, что этот комментарий упрощает понимание алгоритма пользователем, поэтому я склонен оставить его .
    Второй комментарий почти стопроцентно необходим . Он объясняет смысл ис- пользования квадратного корня как верхней границы цикла . Мне не удалось найти ни простого имени переменной, ни другой структуры кода, которые бы наглядно передавали это обстоятельство . С другой стороны, само использование квадратного корня может быть иллюзией . Действительно ли ограничение цикла квадратным корнем способно сэкономить время? Не уйдет ли на его вычисле- ние больше времени, чем я экономлю? Об этом стоит подумать . Использование квадратного корня в качестве верхней границы цикла тешит мои наклонности старого хакера, работавшего на C и ассемблере, но я не уверен, что оно оправдает время и усилия, необходимые читателям кода для его понимания .
    литература
    [KP78]: Kernighan and Plaugher, The Elements of Programming Style, 2d . ed .,
    McGraw-Hill, 1978 .
    101

    Форматирование
    Мы хотим, чтобы читатель, заглянувший «под капот» программы, был поражен увиденным — нашей аккуратностью, логичностью и вниманием к мелочам .
    Мы хотим, чтобы на него произвела впечатление стройность кода . Мы хотим, чтобы он уважительно поднял брови при просмотре модулей . Мы хотим, чтобы наша работа выглядела профессионально . Если вместо этого читатель видит беспорядочную массу кода, словно написанного шайкой пьяных матросов, то он заключит, что такое же неуважение к мелочам проникло и во все остальные аспекты проекта .
    Вы должны позаботиться о том, чтобы ваш код был хорошо отформатирован .
    Выберите набор простых правил, определяющих формат кода, и последовательно
    5
    102

    Цель форматирования
    103
    применяйте их в своей работе . Если вы работаете в составе группы, то группа должна выработать согласованный набор правил форматирования, соблюдаемых всеми участниками . Также полезно иметь средства автоматизации, которые при- меняют правила форматирования за вас .
    цель форматирования
    Прежде всего я твердо заявляю: форматирование кода важно . Оно слишком важ- но, чтобы не обращать на него внимания, и слишком важно, чтобы относиться к нему с религиозным пылом . Форматирование кода направлено на передачу информации, а передача информации является первоочередной задачей про- фессионального разработчика .
    Возможно, вы думали, что первоочередная задача профессионального разработ- чика – «сделать так, чтобы программа заработала» . Надеюсь, к этому моменту книга уже заставила вас отказаться от этих представлений . Функциональность, созданная сегодня, вполне может измениться в следующей версии, но удобо- читаемость вашего кода окажет сильное воздействие на все изменения, которые когда-либо будут внесены . Стиль кодирования и удобочитаемость создают пре- цеденты, которые продолжают влиять на сопровождаемость и расширяемость кода уже после того, как исходный код изменился до неузнаваемости . Стиль и дисциплина программирования продолжают жить, даже если ваш код остался в прошлом . Так какие же аспекты форматирования помогают нам лучше пере- дать свои мысли?
    Вертикальное форматирование
    Начнем с вертикальных размеров . Насколько большим должен быть исходный файл? В Java размер файла тесно связан с размером класса . Мы поговорим о раз- мерах классов, когда речь пойдет о классах, а пока давайте займемся размером файлов .
    Насколько большими должны быть исходные файлы Java? Оказывается, суще- ствует широчайший диапазон размеров и весьма заметные различия в стиле .
    Некоторые из этих различий показаны на рис . 5 .1 .
    На рисунке изображены семь разных проектов: Junit, FitNesse, TestNG, Time and
    Money (Tam), JDepend, Ant и Tomcat . Отрезки, проходящие через прямоуголь- ники, показывают минимальную и максимальную длину файла в каждом проекте .
    Прямоугольник изображает приблизительно одну треть (стандартное откло- нение
    1
    ) от диапазона длин файлов . Середина прямоугольника соответствует
    1
    Прямоугольник представляет диапазон «сигма/2» выше и ниже среднего значения . Да, я знаю, что распределение длин файлов не является нормальным, поэтому стандартное отклонение не может считаться математически точным . Но я и не стремлюсь к точности .
    Я хочу лишь дать представление о происходящем .
    103

    104
    Глава 5 . Форматирование среднему арифметическому . Таким образом, средний размер файла в проекте
    FitNesse составляет около 65 строк, а около трети файлов имеет размер от 40 до
    100+ строк . Наибольший файл FitNesse занимает около 400 строк, а наимень- ший — всего 6 строк . Обратите внимание: на графике используется логарифми- ческая шкала, поэтому незначительные изменения в вертикальной координате подразумевают очень большие изменения в абсолютном размере .
    Рис . 5 .1 . Распределение длин файлов по логарифмической шкале
    (высота прямоугольника = сигма)
    Junit, FitNesse и Time and Money состоят из относительно небольших файлов . Ни один размер файла не превышает 500 строк, а большинство файлов не превышает
    200 строк . Напротив, в Tomcat и Ant встречаются файлы из нескольких тысяч строк, а около половины имеет длину более 200 строк .
    Что это означает для нас? То, что достаточно серьезную систему (объем FitNesse приближается к 50 000 строк) можно построить из файлов, типичная длина ко- торых составляет 200 строк, с верхним пределом в 500 строк . Хотя это не должно считаться раз и навсегда установленным правилом, такие показатели весьма желательны . Маленькие файлы обычно более понятны, чем большие .
    Газетная метафора
    Представьте себе хорошо написанную газетную статью . Естественно, статья чи- тается по вертикали . В самом начале обычно располагается заголовок с общей темой статьи; он помогает вам решить, представляет ли статья интерес для вас .
    В первом абзаце приводится краткое изложение сюжета на уровне общих кон- цепций, без приведения каких-либо подробностей . По мере продвижения к кон-
    104

    Цель форматирования
    105
    цу статьи объем детализации непрерывно растет, пока вы не узнаете все даты, имена, цитаты и т . д .
    Исходный файл должен выглядеть как газетная статья . Имя файла должно быть простым, но содержательным . Одного имени должно быть достаточно для того, чтобы читатель понял, открыл ли он нужный модуль или нет . Начальные блоки исходного файла описывают высокоуровневые концепции и алгоритмы . Степень детализации увеличивается при перемещении к концу файла, а в самом конце собираются все функции и подробности низшего уровня в исходном файле .
    Газета состоит из множества статей, в большинстве своем очень коротких . Другие статьи чуть длиннее . И лишь немногие статьи занимают всю газетную страницу .
    Собственно, именно этим газеты так удобны . Если бы они состояли из одной длинной статьи с неупорядоченной подборкой фактов, дат и имен, то мы бы про- сто не смогли их читать .
    Вертикальное разделение концепций
    Практически весь код читается слева направо и сверху вниз . Каждая строка представляет выражение или условие, а каждая группа строк представляет за- конченную мысль . Эти мысли следует отделять друг от друга пустыми строками .
    Для примера возьмем листинг 5 .1 . Объявление пакета, директива(-ы) импорта и все функции разделяются пустыми строками . Это чрезвычайно простое пра- вило оказывает глубокое воздействие на визуальную структуру кода . Каждая пустая строка становится зрительной подсказкой, указывающей на начало новой самостоятельной концепции . В ходе просмотра листинга ваш взгляд привлекает первая строка, следующая за пустой строкой .
    листинг 5 .1 . BoldWidget.java package fitnesse.wikitext.widgets;
    import java.util.regex.*;
    public class BoldWidget extends ParentWidget {
    public static final String REGEXP = "'''.+?'''";
    private static final Pattern pattern = Pattern.compile("'''(.+?)'''",
    Pattern.MULTILINE + Pattern.DOTALL
    };
    public BoldWidget(ParentWidget parent, String text) throws Exception {
    super(parent);
    Matcher match = pattern.matcher(text);
    match.find();
    addChildWidgets(match.group(1));
    }
    продолжение
    105

    106
    Глава 5 . Форматирование
    листинг 5 .1 (продолжение)
    public String render() throws Exception {
    StringBuffer html = new StringBuffer("");
    html.append(childHtml()).append("
    ");
    return html.toString();
    }
    }
    Удаление пустых строк, как в листинге 5 .2, имеет весьма тяжелые последствия для удобочитаемости кода .
    листинг 5 .2 . BoldWidget.java package fitnesse.wikitext.widgets;
    import java.util.regex.*;
    public class BoldWidget extends ParentWidget {
    public static final String REGEXP = "'''.+?'''";
    private static final Pattern pattern = Pattern.compile("'''(.+?)'''",
    Pattern.MULTILINE + Pattern.DOTALL);
    public BoldWidget(ParentWidget parent, String text) throws Exception {
    super(parent);
    Matcher match = pattern.matcher(text);
    match.find();
    addChildWidgets(match.group(1));}
    public String render() throws Exception {
    StringBuffer html = new StringBuffer("");
    html.append(childHtml()).append("
    ");
    return html.toString();
    }
    }
    Эффект становится еще более заметным, если на секунду отвести глаза от ли- стинга . В первом примере группировка строк сразу бросается в глаза, а второй пример выглядит как сплошная каша, притом что два листинга различаются только вертикальными разделителями .
    Вертикальное сжатие
    Если вертикальные пропуски разделяют концепции, то вертикальное сжатие под- черкивает тесные связи . Таким образом, строки кода, между которыми существу- ет тесная связь, должны быть «сжаты» по вертикали . Обратите внимание на то, как бесполезные комментарии в листинге 5 .3 нарушают группировку двух пере- менных экземпляров .
    листинг 5 .3
    public class ReporterConfig {
    /**
    * Имя класса слушателя
    */
    106

    Цель форматирования
    107
    private String m_className;
    /**
    * Свойства слушателя
    */
    private List m_properties = new ArrayList
    ();
    public void addProperty(Property property) {
    m_properties.add(property);
    }
    Листинг 5 .4 читается гораздо проще . Он нормально воспринимается «с одного взгляда» — по крайней мере, для меня . Я смотрю на него и сразу вижу, что пере- до мной класс с двумя переменными и одним методом; для этого мне не прихо- дится вертеть головой или бегать по строчкам глазами . В предыдущем листинге для достижения того же уровня понимания приходится потрудиться намного больше .
    листинг 5 .4
    public class ReporterConfig {
    private String m_className;
    private List m_properties = new ArrayList
    ();
    public void addProperty(Property property) {
    m_properties.add(property);
    }
    Вертикальные расстояния
    Вам когда-нибудь доводилось метаться по классу, прыгая от одной функции к другой, прокручивая исходный файл вверх-вниз, пытаясь разобраться, как функции связаны друг с другом и как они работают, — только для того, чтобы окончательно заблудиться в его запутанных нагромождениях? Когда-нибудь искали определение функции или переменной по цепочкам наследования? Все это крайне неприятно, потому что вы стараетесь понять, как работает система, а вместо этого вам приходится тратить время и интеллектуальные усилия на поиски и запоминание местонахождения отдельных фрагментов .
    Концепции, тесно связанные друг с другом, должны находиться поблизости друг от друга по вертикали [G10] . Разумеется, это правило не работает для концепций, находящихся в разных файлах . Но тесно связанные концепции и не должны находиться в разных файлах, если только это не объясняется очень вескими до- водами . Кстати, это одна из причин, по которой следует избегать защищенных переменных .
    Если концепции связаны настолько тесно, что они находятся в одном исходном файле, их вертикальное разделение должно показывать, насколько они важны для понимания друг друга . Не заставляйте читателя прыгать туда-сюда по ис- ходным файлам и классам .
    107

    108
    Глава 5 . Форматирование
    Объявления переменных. Переменные следует объявлять как можно ближе к месту использования . Так как мы имеем дело с очень короткими функциями, локальные переменные должны перечисляться в начале каждой функции, как в следующем примере из Junit 4 .3 .
    private static void readPreferences() {
    InputStream is= null;
    try {
    is= new FileInputStream(getPreferencesFile());
    setPreferences(new Properties(getPreferences()));
    getPreferences().load(is);
    } catch (IOException e) {
    try {
    if (is != null)
    is.close();
    } catch (IOException e1) {
    }
    }
    }
    Управляющие переменные циклов обычно объявляются внутри конструкции цикла, как в следующей симпатичной маленькой функции из того же источника .
    public int countTestCases() {
    int count= 0;
    for (Test each : tests)
    count += each.countTestCases();
    return count;
    }
    В отдельных случаях переменная может объявляться в начале блока или непо- средственно перед циклом в длинной функции . Пример такого объявления пред- ставлен в следующем фрагменте очень длинной функции из TestNG .
    for (XmlTest test : m_suite.getTests()) {
    TestRunner tr = m_runnerFactory.newTestRunner(this, test);
    tr.addListener(m_textReporter);
    m_testRunners.add(tr);
    invoker = tr.getInvoker();
    for (ITestNGMethod m : tr.getBeforeSuiteMethods()) {
    beforeSuiteMethods.put(m.getMethod(), m);
    }
    for (ITestNGMethod m : tr.getAfterSuiteMethods()) {
    afterSuiteMethods.put(m.getMethod(), m);
    }
    }
    Переменные экземпляров, напротив, должны объявляться в начале класса .
    Это не увеличивает вертикальное расстояние между переменными, потому что
    108

    Цель форматирования
    109
    в хорошо спроектированном классе они используются многими, если не всеми, методами класса .
    Размещение переменных экземпляров становилось причиной ожесточенных споров . В C++ обычно применялось так называемое правило ножниц, при кото- ром все переменные экземпляров размещаются внизу . С другой стороны, в Java они обычно размещаются в начале класса . Я не вижу причин для использования каких-либо других конвенций . Здесь важно то, что переменные экземпляров должны объявляться в одном хорошо известном месте . Все должны знать, где следует искать объявления .
    Для примера рассмотрим странный класс TestSuite из JUnit 4 .3 .1 . Я основательно сократил этот класс, чтобы лучше выразить свою мысль . Где-то в середине ли- стинга вдруг обнаруживаются объявления двух переменных экземпляров . Если бы автор сознательно хотел спрятать их, трудно найти более подходящее место .
    Читатель кода может наткнуться на эти объявления только случайно (как я) .
    public class TestSuite implements Test {
    static public Test createTest(Class theClass,
    String name) {
    }
    public static Constructor getTestConstructor(Class theClass) throws NoSuchMethodException {
    }
    public static Test warning(final String message) {
    }
    private static String exceptionToString(Throwable t) {
    }
    private String fName;
    private Vector fTests= new Vector(10);
    public TestSuite() {
    }
    public TestSuite(final Class theClass) {
    }
    public TestSuite(Class theClass, String name) {
    }
    }
    109

    110
    Глава 5 . Форматирование
    Зависимые функции. Если одна функция вызывает другую, то эти функции должны располагаться вблизи друг от друга по вертикали, а вызывающая функ- ция должна находиться над вызываемой (если это возможно) . Тем самым фор- мируется естественная структура программного кода . Если это правило будет последовательно соблюдаться, читатели кода будут уверены в том, что опреде- ления функций следуют неподалеку от их вызовов . Для примера возьмем фраг- мент FitNesse из листинга 5 .5 . Обратите внимание на то, как верхняя функция вызывает нижние, и как они, в свою очередь, вызывают функции более низкого уровня . Такая структура позволяет легко найти вызываемые функции и значи- тельно улучшает удобочитаемость всего модуля .
    1   ...   9   10   11   12   13   14   15   16   ...   49


    написать администратору сайта