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

  • Вертикальное упорядочение

  • Горизонтальное форматирование

  • Горизонтальное разделение и сжатие

  • Горизонтальное выравнивание

  • Вырожденные области видимости

  • Правила форматирования от дядюшки Боба

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


    Скачать 3.16 Mb.
    НазваниеСоздание, анализ ирефакторинг
    Дата29.09.2022
    Размер3.16 Mb.
    Формат файлаpdf
    Имя файлаChistyj_kod_-_Sozdanie_analiz_i_refaktoring_(2013).pdf
    ТипКнига
    #706087
    страница14 из 49
    1   ...   10   11   12   13   14   15   16   17   ...   49
    листинг 5 .5 . WikiPageResponder.java public class WikiPageResponder implements SecureResponder {
    protected WikiPage page;
    protected PageData pageData;
    protected String pageTitle;
    protected Request request;
    protected PageCrawler crawler;
    public Response makeResponse(FitNesseContext context, Request request)
    throws Exception {
    String pageName = getPageNameOrDefault(request, "FrontPage");
    loadPage(pageName, context);
    if (page == null)
    return notFoundResponse(context, request);
    else return makePageResponse(context);
    }
    private String getPageNameOrDefault(Request request, String defaultPageName)
    {
    String pageName = request.getResource();
    if (StringUtil.isBlank(pageName))
    pageName = defaultPageName;
    return pageName;
    }
    protected void loadPage(String resource, FitNesseContext context)
    throws Exception {
    WikiPagePath path = PathParser.parse(resource);
    crawler = context.root.getPageCrawler();
    crawler.setDeadEndStrategy(new VirtualEnabledPageCrawler());
    page = crawler.getPage(context.root, path);
    if (page != null)
    pageData = page.getData();
    }
    private Response notFoundResponse(FitNesseContext context, Request request)
    throws Exception {
    110

    Цель форматирования
    111
    return new NotFoundResponder().makeResponse(context, request);
    }
    private SimpleResponse makePageResponse(FitNesseContext context)
    throws Exception {
    pageTitle = PathParser.render(crawler.getFullPath(page));
    String html = makeHtml(context);
    SimpleResponse response = new SimpleResponse();
    response.setMaxAge(0);
    response.setContent(html);
    return response;
    }
    Заодно этот фрагмент дает хороший пример хранения констант на соответствую- щем уровне [G35] . Константу «FrontPage» можно было бы объявить в функции getPageNameOrDefault
    , но тогда хорошо известная и ожидаемая константа оказалась бы погребенной в функции неуместно низкого уровня . Лучше переместить эту константу вниз – от того места, где ее следовало бы ввести, к месту ее фактиче- ского использования .
    Концептуальное родство. Некоторые фрагменты кода требуют, чтобы их раз- местили вблизи от других фрагментов . Такие фрагменты обладают определен- ным концептуальным родством . Чем сильнее родство, тем меньше должно быть вертикальное расстояние между ними .
    Как мы уже видели, родство может быть основано на прямой зависимости (когда одна функция вы- зывает другую) или на использовании перемен- ных в функциях . Однако существуют и другие разновидности родства . Например, родство воз- никает в том случае, если группа функций вы- полняет аналогичные операции . Возьмем следую- щий фрагмент кода из Junit 4 .3 .1:
    public class Assert {
    static public void assertTrue(String message, boolean condition) {
    if (!condition)
    fail(message);
    }
    static public void assertTrue(boolean condition) {
    assertTrue(null, condition);
    }
    static public void assertFalse(String message, boolean condition) {
    assertTrue(message, !condition);
    }
    111

    112
    Глава 5 . Форматирование static public void assertFalse(boolean condition) {
    assertFalse(null, condition);
    }
    Эти функции обладают сильным концептуальным родством, потому что они используют единую схему выбора имен и выполняют разные варианты одной базовой операции . Тот факт, что они вызывают друг друга, вторичен . Даже без него эти функции все равно следовало бы разместить поблизости друг от друга .
    Вертикальное упорядочение
    Как правило, взаимозависимые функции должны размещаться в нисходящем порядке . Иначе говоря, вызываемая функция должна располагаться ниже вы- зывающей функции

    . Так формируется логичная структура модуля исходного кода – от высокого уровня к более низкому .
    Как и в газетных статьях, читатель ожидает, что самые важные концепции бу- дут изложены сначала, причем с минимальным количеством второстепенных де талей . Низкоуровневые подробности естественно приводить в последнюю очередь . Это позволяет нам бегло просматривать исходные файлы, извлекая суть из нескольких начальных функций, без погружения в подробности . Ли- стинг 5 .5 имеет именно такую структуру . Возможно, еще лучшие примеры встречаются в листинге 15 .5 на с . 299 и в листинге 3 .7 на с . 75 .
    Горизонтальное форматирование
    Насколько широкой должна быть строка? Чтобы ответить на этот вопрос, мы проанализируем ширину строк в типичных программах . Как и в предыдущем случае, будут проанализированы семь разных проектов . На рис . 5 .2 показано рас- пределение длин строк во всех семи проектах . Закономерность впечатляет, осо- бенно около 45 символов . Фактически каждый размер от 20 до 60 соответствует примерно одному проценту от общего количества строк . Целых 40 процентов!
    Возможно, еще 30 процентов составляют строки с длиной менее 10 символов .
    Помните, что на графике используется логарифмическая шкала, поэтому разброс в области свыше 80 символов очень важен . Программисты явно предпочитают более короткие строки .
    Это наводит на мысль, что строки лучше делать по возможности короткими .
    Установленное Холлеритом старое ограничение в 80 символов выглядит излишне жестким; я ничего не имеют против строк длиной в 100 и даже 120 символов . Но более длинные строки, вероятно, вызваны небрежностью программиста .
    112

    Горизонтальное форматирование
    113
    Рис . 5 .2 . Распределение ширины строк в Java
    Прежде я использовал это правило, чтобы мне не приходилось прокручивать про- граммный код вправо . Но современные мониторы стали настолько широкими, а молодые программисты выбирают настолько мелкие шрифты, что на экране по- мещается до 200 символов . Не делайте этого . Лично я установил себе «верхнюю планку» в 120 символов .
    Горизонтальное разделение и сжатие
    Горизонтальные пропуски используются для группировки взаимосвязанных элементов и разделения разнородных элементов . Рассмотрим следующую функцию:
    private void measureLine(String line) {
    lineCount++;
    int lineSize = line.length();
    totalChars += lineSize;
    lineWidthHistogram.addLine(lineSize, lineCount);
    recordWidestLine(lineSize);
    }
    Знаки присваивания окружены пробелами, обеспечивающими их визуальное выделение . Операторы присваивания состоят из двух основных элементов: левой и правой частей . Пробелы наглядно подчеркивают это разделение .
    С другой стороны, я не стал отделять имена функций от открывающих скобок .
    Это обусловлено тем, что имя функции тесно связано с ее аргументами . Пробе- лы изолируют их вместо того, чтобы объединять . Я также разделил аргументы
    113

    114
    Глава 5 . Форматирование в скобках пробелами, чтобы выделить запятые и подчеркнуть, что аргументы не зависят друг от друга .
    Пробелы также применяются для визуального обозначения приоритета опера- торов:
    public class Quadratic {
    public static double root1(double a, double b, double c) {
    double determinant = determinant(a, b, c);
    return (-b + Math.sqrt(determinant)) / (2*a);
    }
    public static double root2(int a, int b, int c) {
    double determinant = determinant(a, b, c);
    return (-b - Math.sqrt(determinant)) / (2*a);
    }
    private static double determinant(double a, double b, double c) {
    return b*b - 4*a*c;
    }
    }
    Обратите внимание, как хорошо читаются формулы . Между множителями нет пробелов, потому что они обладают высоким приоритетом . Слагаемые разделя- ются пробелами, так как сложение и вычитание имеют более низкий приоритет .
    К сожалению, в большинстве программ форматирования кода приоритет опе- раторов не учитывается, и во всех случаях применяются одинаковые пропуски .
    Нетривиальные изменения расстояний, как в приведенном примере, теряются после переформатирования кода .
    Горизонтальное выравнивание
    Когда я был ассемблерным программистом
    1
    , горизонтальное выравнивание использовалось для визуального выделения некоторых структур . Когда я пере- шел на C, C++, а в конце концов и на Java, я продолжал выравнивать все имена переменных в группах объявлений или все правосторонние значения в группах команд присваивания . Мой код выглядел примерно так:
    public class FitNesseExpediter implements ResponseSender
    {
    private Socket socket;
    private InputStream input;
    private OutputStream output;
    private Request request;
    private Response response;
    private FitNesseContext context;
    protected long requestParsingTimeLimit;
    private long requestProgress;
    1
    Кого я пытаюсь обмануть? Я так и остался ассемблерным программистом . Парня можно разлучить с «металлом», но в душе «металл» все равно живет!
    114

    Горизонтальное форматирование
    115
    private long requestParsingDeadline;
    private boolean hasError;
    public FitNesseExpediter(Socket s,
    FitNesseContext context) throws Exception
    {
    this.context = context;
    socket = s;
    input = s.getInputStream();
    output = s.getOutputStream();
    requestParsingTimeLimit = 10000;
    }
    Однако потом я обнаружил, что такое выравнивание не приносит пользы . Оно визуально выделяет совсем не то, что требуется, и отвлекает читателя от моих истинных намерений . Например, в приведенном выше списке объявлений чита- тель просматривает имена переменных, не обращая внимания на их типы . Анало- гичным образом в списке команд присваивания возникает соблазн просмотреть правосторонние значения, не замечая оператора присваивания . Ситуация усу- губляется тем, что средства автоматического форматирования обычно удаляют подобное выравнивание .
    Поэтому в итоге я отказался от этого стиля форматирования . Сейчас я отдаю предпочтение невыровненным объявлениям и присваиваниям, как в следую- щем фрагменте, потому что они помогают выявить один важный дефект . Если в программе встречаются длинные списки, нуждающиеся в выравнивании, то проблема кроется в длине списка, а не в отсутствии выравнивания . Длина спи- сков объявлений в классе FitNesseExpediter наводит на мысль, что этот класс необходимо разделить .
    public class FitNesseExpediter implements ResponseSender
    {
    private Socket socket;
    private InputStream input;
    private OutputStream output;
    private Request request;
    private Response response;
    private FitNesseContext context;
    protected long requestParsingTimeLimit;
    private long requestProgress;
    private long requestParsingDeadline;
    private boolean hasError;
    public FitNesseExpediter(Socket s, FitNesseContext context) throws Exception
    {
    this.context = context;
    socket = s;
    input = s.getInputStream();
    output = s.getOutputStream();
    requestParsingTimeLimit = 10000;
    }
    115

    116
    Глава 5 . Форматирование
    Отступы
    Исходный файл имеет иерархическую структуру . В нем присутствует ин- формация, относящаяся к файлу в целом; к отдельным классам в файле; к мето- дам внутри классов; к блокам внутри методов и рекурсивно – к блокам внутри блоков . Каждый уровень этой иерархии образует область видимости, в кото- рой могут объявляться имена и в которой интерпретируются исполняемые команды .
    Чтобы создать наглядное представление этой иерархии, мы снабжаем строки ис- ходного кода отступами, размер которых соответствует их позиции в иерархии .
    Команды уровня файла (такие, как большинство объявлений классов) отступов не имеют . Методы в классах сдвигаются на один уровень вправо от уровня клас- са . Реализации этих методов сдвигаются на один уровень вправо от объявления класса . Реализации блоков сдвигаются на один уровень вправо от своих внишних блоков и т . д .
    Программисты широко используют эту схему расстановки отступов в своей ра- боте . Чтобы определить, к какой области видимости принадлежат строки кода, они визуально группируют строки по левому краю . Это позволяет им быстро пропускать области видимости, не относящиеся к текущей ситуации (например, реализации команд if и while
    ) . У левого края ищутся объявления новых методов, новые переменные и даже новые классы . Без отступов программа становится практически нечитаемой для людей . Следующие программы идентичны с син- таксической и семантической точки зрения:
    public class FitNesseServer implements SocketServer { private FitNesseContext context; public FitNesseServer(FitNesseContext context) { this.context = context; } public void serve(Socket s) { serve(s, 10000); } public void serve(Socket s, long requestTimeout) { try { FitNesseExpediter sender = new
    FitNesseExpediter(s, context); sender.setRequestParsingTimeLimit(requestTimeout); sender.start(); } catch(Exception e) { e.printStackTrace(); } } }
    ----- public class FitNesseServer implements SocketServer {
    private FitNesseContext context;
    public FitNesseServer(FitNesseContext context) {
    this.context = context;
    }
    public void serve(Socket s) {
    serve(s, 10000);
    }
    public void serve(Socket s, long requestTimeout) {
    try {
    FitNesseExpediter sender = new FitNesseExpediter(s, context);
    sender.setRequestParsingTimeLimit(requestTimeout);
    sender.start();
    116

    Горизонтальное форматирование
    117
    }
    catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
    Наше зрение быстро охватывает структуру файла с отступами . Мы почти мгно- венно находим переменные, конструкторы и методы . Всего за несколько секунд можно понять, что класс предоставляет простой интерфейс для работы с сокетом, с поддержкой тайм-аута . С другой стороны, разобраться в версии без отступов без тщательного анализа практически невозможно .
    Нарушения отступов. Иногда возникает соблазн нарушить правила расста- новки отступов в коротких командах if
    , коротких циклах while или коротких функциях . Но каждый раз, когда я поддавался этому искушению, я почти всегда возвращался и расставлял отступы, как положено . Таким образом, я стараюсь не сворачивать блоки в одну строку, как в этом фрагменте:
    public class CommentWidget extends TextWidget
    {
    public static final String REGEXP = "^#[^\r\n]*(?:(?:\r\n)|\n|\r)?";
    public CommentWidget(ParentWidget parent, String text){super(parent, text);}
    public String render() throws Exception {return ""; }
    }
    Вместо этого я предпочитаю развернутые блоки с правильными отступами:
    public class CommentWidget extends TextWidget {
    public static final String REGEXP = "^#[^\r\n]*(?:(?:\r\n)|\n|\r)?";
    public CommentWidget(ParentWidget parent, String text) {
    super(parent, text);
    }
    public String render() throws Exception {
    return "";
    }
    }
    Вырожденные области видимости
    Иногда тело цикла while или команды for не содержит команд, то есть является вырожденным, как в следующем фрагменте . Я не люблю такие структуры и ста- раюсь избегать их . А когда это невозможно, я по крайней мере слежу за тем, чтобы пустое тело имело правильные отступы и было заключено в фигурные скобки . Вы не представляете, как часто меня обманывала точка с запятой, молчаливо прячу- щаяся в конце цикла while в той же строке . Если не сделать эту точку хорошо за- метной, разместив ее в отдельной строке, ее попросту слишком сложно разглядеть:
    while (dis.read(buf, 0, readBufferSize) != -1)
    ;
    117

    118
    Глава 5 . Форматирование
    Правила форматирования в группах
    У каждого программиста есть свои люби- мые правила форматирования, но если он работает в группе, то должен руководство- ваться групповыми правилами .
    Группа разработчиков согласует единый стиль форматирования, который в даль- нейшем применяется всеми участниками .
    Код программного продукта должен быть оформлен в едином стиле . Он не должен выглядеть так, словно был написан несколькими личностями, расходящимися во мнениях по поводу оформления .
    В начале работы над проектом FitNesse в 2002 году я провел встречу с группой для выработки общего стиля программирования . На это потребовалось около 10 минут . Мы решили, где будем расставлять фигурные скобки, каким будет размер отступов, по какой схеме будут присваиваться имена классов, переменных и мето- дов и т . д . Затем эти правила были закодированы в системе форматирования кода нашей рабочей среды, и в дальнейшем мы неуклонно придерживались их . Это были не те правила, которые предпочитаю лично я; это были правила, выбранные группой . И я, как участник группы, неуклонно соблюдал их при написании кода в проекте FitNesse .
    Хорошая программная система состоит из набора удобочитаемых документов, оформленных в едином, согласованном стиле . Читатель должен быть уверен в том, что форматные атрибуты, встречающиеся в одном исходном файле, будут иметь точно такой же смысл в других файлах . Ни в коем случае не усложняйте исходный код, допуская его оформление в нескольких разных стилях .
    Правила форматирования от дядюшки Боба
    Правила, которые использую лично я, очень просты; они представлены в коде листинга 5 .6 . Перед вами пример того, как сам код становится лучшим докумен- том, описывающим стандарты кодирования .
    листинг 5 .6 . CodeAnalyzer.java public class CodeAnalyzer implements JavaFileAnalysis {
    private int lineCount;
    private int maxLineWidth;
    private int widestLineNumber;
    private LineWidthHistogram lineWidthHistogram;
    private int totalChars;
    public CodeAnalyzer() {
    lineWidthHistogram = new LineWidthHistogram();
    }
    118

    Горизонтальное форматирование
    119
    public static List findJavaFiles(File parentDirectory) {
    List files = new ArrayList();
    findJavaFiles(parentDirectory, files);
    return files;
    }
    private static void findJavaFiles(File parentDirectory, List files) {
    for (File file : parentDirectory.listFiles()) {
    if (file.getName().endsWith(".java"))
    files.add(file);
    else if (file.isDirectory())
    findJavaFiles(file, files);
    }
    }
    public void analyzeFile(File javaFile) throws Exception {
    BufferedReader br = new BufferedReader(new FileReader(javaFile));
    String line;
    while ((line = br.readLine()) != null)
    measureLine(line);
    }
    private void measureLine(String line) {
    lineCount++;
    int lineSize = line.length();
    totalChars += lineSize;
    lineWidthHistogram.addLine(lineSize, lineCount);
    recordWidestLine(lineSize);
    }
    private void recordWidestLine(int lineSize) {
    if (lineSize > maxLineWidth) {
    maxLineWidth = lineSize;
    widestLineNumber = lineCount;
    }
    }
    public int getLineCount() {
    return lineCount;
    }
    public int getMaxLineWidth() {
    return maxLineWidth;
    }
    public int getWidestLineNumber() {
    return widestLineNumber;
    }
    public LineWidthHistogram getLineWidthHistogram() {
    return lineWidthHistogram;
    }
    продолжение
    119

    1   ...   10   11   12   13   14   15   16   17   ...   49


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