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

  • Внутреннее строение JUnit

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


    Скачать 3.16 Mb.
    НазваниеСоздание, анализ ирефакторинг
    Дата29.09.2022
    Размер3.16 Mb.
    Формат файлаpdf
    Имя файлаChistyj_kod_-_Sozdanie_analiz_i_refaktoring_(2013).pdf
    ТипКнига
    #706087
    страница32 из 49
    1   ...   28   29   30   31   32   33   34   35   ...   49
    283
    }
    return "";
    }
    public enum ErrorCode {
    OK, INVALID_FORMAT, UNEXPECTED_ARGUMENT, INVALID_ARGUMENT_NAME,
    MISSING_STRING,
    MISSING_INTEGER, INVALID_INTEGER,
    MISSING_DOUBLE, INVALID_DOUBLE}
    }
    листинг 14 .16 . Args.java public class Args {
    private String schema;
    private Map marshalers = new HashMap();
    private Set argsFound = new HashSet();
    private Iterator currentArgument;
    private List argsList;
    public Args(String schema, String[] args) throws ArgsException {
    this.schema = schema;
    argsList = Arrays.asList(args);
    parse();
    }
    private void parse() throws ArgsException {
    parseSchema();
    parseArguments();
    }
    private boolean parseSchema() throws ArgsException {
    for (String element : schema.split(",")) {
    if (element.length() > 0) {
    parseSchemaElement(element.trim());
    }
    }
    return true;
    }
    private void parseSchemaElement(String element) throws ArgsException {
    char elementId = element.charAt(0);
    String elementTail = element.substring(1);
    validateSchemaElementId(elementId);
    if (elementTail.length() == 0)
    marshalers.put(elementId, new BooleanArgumentMarshaler());
    else if (elementTail.equals("*"))
    marshalers.put(elementId, new StringArgumentMarshaler());
    else if (elementTail.equals("#"))
    marshalers.put(elementId, new IntegerArgumentMarshaler());
    else if (elementTail.equals("##"))
    продолжение
    283

    284
    Глава 14 . Последовательное очищение
    листинг 14 .16 (продолжение)
    marshalers.put(elementId, new DoubleArgumentMarshaler());
    else throw new ArgsException(ArgsException.ErrorCode.INVALID_FORMAT, elementId, elementTail);
    }
    private void validateSchemaElementId(char elementId) throws ArgsException {
    if (!Character.isLetter(elementId)) {
    throw new ArgsException(ArgsException.ErrorCode.INVALID_ARGUMENT_NAME, elementId, null);
    }
    }
    private void parseArguments() throws ArgsException {
    for (currentArgument = argsList.iterator(); currentArgument.hasNext();) {
    String arg = currentArgument.next();
    parseArgument(arg);
    }
    }
    private void parseArgument(String arg) throws ArgsException {
    if (arg.startsWith("-"))
    parseElements(arg);
    }
    private void parseElements(String arg) throws ArgsException {
    for (int i = 1; i < arg.length(); i++)
    parseElement(arg.charAt(i));
    }
    private void parseElement(char argChar) throws ArgsException {
    if (setArgument(argChar))
    argsFound.add(argChar);
    else {
    throw new ArgsException(ArgsException.ErrorCode.UNEXPECTED_ARGUMENT, argChar, null);
    }
    }
    private boolean setArgument(char argChar) throws ArgsException {
    ArgumentMarshaler m = marshalers.get(argChar);
    if (m == null)
    return false;
    try {
    m.set(currentArgument);
    return true;
    } catch (ArgsException e) {
    e.setErrorArgumentId(argChar);
    throw e;
    }
    }
    284

    Аргументы String
    285
    public int cardinality() {
    return argsFound.size();
    }
    public String usage() {
    if (schema.length() > 0)
    return "-[" + schema + "]";
    else return "";
    }
    public boolean getBoolean(char arg) {
    ArgumentMarshaler am = marshalers.get(arg);
    boolean b = false;
    try {
    b = am != null && (Boolean) am.get();
    } catch (ClassCastException e) {
    b = false;
    }
    return b;
    }
    public String getString(char arg) {
    ArgumentMarshaler am = marshalers.get(arg);
    try {
    return am == null ? "" : (String) am.get();
    } catch (ClassCastException e) {
    return "";
    }
    }
    public int getInt(char arg) {
    ArgumentMarshaler am = marshalers.get(arg);
    try {
    return am == null ? 0 : (Integer) am.get();
    } catch (Exception e) {
    return 0;
    }
    }
    public double getDouble(char arg) {
    ArgumentMarshaler am = marshalers.get(arg);
    try {
    return am == null ? 0 : (Double) am.get();
    } catch (Exception e) {
    return 0.0;
    }
    }
    public boolean has(char arg) {
    return argsFound.contains(arg);
    }
    }
    285

    286
    Глава 14 . Последовательное очищение
    Основные изменения в классе
    Args свелись к удалениям . Большая часть кода ушла из
    Args в
    ArgsException
    . Хорошо . Мы также переместили все разновидности
    ArgumentMarshaller в отдельные файлы . Еще лучше!
    Одним из важнейших аспектов хорошей программной архитектуры является логическое разбиение кода — создание подходящих мест для размещения разных кодовых блоков . Разделение ответственности заметно упрощает понимание и со- провождение кода .
    Обратите внимание на метод errorMessage класса
    ArgsException
    . Очевидно, раз- мещение форматирования сообщения об ошибках нарушает принцип единой ответственности . Класс
    Args должен заниматься обработкой аргументов, а не форматом сообщений об ошибках . Но насколько логично размещать код форма- тирования сообщений в
    ArgsException
    ?
    Откровенно говоря, это компромиссное решение . Пользователям, которым не нравится, что сообщения об ошибках поставляет класс
    ArgsException
    , придется написать собственную реализацию .
    К этому моменту мы уже вплотную подошли к окончательному решению, приве- денному в начале этой главы . Завершающие преобразования остаются читателю для самостоятельных упражнений .
    Заключение
    Заставить код работать недостаточно . Работоспособный код часто несовершенен .
    Программисты, которые заставляют свой код работать и на этом считают свою задачу выполненной, ведут себя непрофессионально . Возможно, они опасаются, что у них не хватит времени для совершенствования структуры и архитектуры кода, но я не могу с этим согласиться . Ничто не оказывает настолько всесторон- него и длительного отрицательного влияния на судьбу программного проекта, как плохой код . Плохой график можно переделать, плохие требования можно переопределить . Плохую динамику рабочей группы еще можно исправить . Пло- хой код загнивает и разбухает, превращаясь в беспощадный груз, который тянет группу ко дну . Сколько раз я видел, как работа заходит в тупик по одной причине: в спешке вместо добротного кода создавалась какая-то безобразная мешанина, которая после этого обрекала группу на бесконечные мучения .
    Конечно, плохой код можно вычистить . Но это обходится очень дорого . В про- цессе загнивания кода модули постепенно проникают друг в друга, образуется множество скрытых и запутанных зависимостей . Поиск и разрыв старых зависи- мостей — длительная, тяжелая работа . С другой стороны, поддерживать чистоту в коде относительно несложно . Если утром вы устроили беспорядок в модуле, то его будет легко вычистить днем . Или еще лучше, если вы устроили беспорядок пять минут назад, то его будет очень легко вычистить прямо сейчас .
    Итак, постоянно следите за тем, чтобы ваш код оставался как можно более про- стым и чистым . Не допускайте, чтобы он начал загнивать .
    286

    Внутреннее строение
    JUnit
    JUnit — одна из самых известных инфраструктур для языка Java . Как и положено нормальной инфраструктуре, она концептуально проста, точна в определениях и элегантна в реализации . Но как выглядит ее код? В этой главе мы покритикуем пример, взятый из инфраструктуры JUnit .
    15
    287

    288
    Глава 15 . Внутреннее строение JUnit
    Инфраструктура JUnit
    У JUnit много авторов, но все началось с совместного перелета Кента Бека и Эрика Гамма в Атланту . Кент хотел освоить Java, а Эрик собирался заняться изучением тестовой инфраструктуры Кента для языка Smalltalk . «А что может быть более естественным для двух „технарей“, запертых в тесном пространстве, чем достать портативные компьютеры и взяться за программирование?
    1
    » За три часа «высотной работы» были написаны основы JUnit .
    Модуль, который мы рассмотрим в этой главе, предназначен для выявления ошибок сравнения строк . Он называется
    ComparisonCompactor
    . Получив две раз- личающиеся строки (например,
    ABCDE
    и
    ABXDE
    ), он выдает сводку различий между ними, генерируя строку вида
    <...B[X]D...>
    Я мог бы объяснить и подробнее, но тестовые сценарии сделают это лучше . Про- смотрите листинг 15 .1 и вы отлично поймете требования этого модуля . А заодно критически проанализируйте структуру тестов . Нельзя ли упростить их, сделать более наглядными?
    листинг 15 .1 . ComparisonCompactorTest.java package junit.tests.framework;
    import junit.framework.ComparisonCompactor;
    import junit.framework.TestCase;
    public class ComparisonCompactorTest extends TestCase {
    public void testMessage() {
    String failure= new ComparisonCompactor(0, "b", "c").compact("a");
    assertTrue("a expected:<[b]> but was:<[c]>".equals(failure));
    }
    public void testStartSame() {
    String failure= new ComparisonCompactor(1, "ba", "bc").compact(null);
    assertEquals(«expected: but was:», failure);
    }
    public void testEndSame() {
    String failure= new ComparisonCompactor(1, "ab", "cb").compact(null);
    assertEquals("expected:<[a]b> but was:<[c]b>", failure);
    }
    public void testSame() {
    String failure= new ComparisonCompactor(1, "ab", "ab").compact(null);
    assertEquals("expected: but was:", failure);
    }
    public void testNoContextStartAndEndSame() {
    1
    JUnit Pocket Guide, Kent Beck, O’Reilly, 2004, c . 43 .
    288

    Инфраструктура JUnit
    289
    String failure= new ComparisonCompactor(0, "abc", "adc").compact(null);
    assertEquals("expected:<...[b]...> but was:<...[d]...>", failure);
    }
    public void testStartAndEndContext() {
    String failure= new ComparisonCompactor(1, "abc", "adc").compact(null);
    assertEquals("expected: but was:", failure);
    }
    public void testStartAndEndContextWithEllipses() {
    String failure= new ComparisonCompactor(1, "abcde", "abfde").compact(null);
    assertEquals("expected:<...b[c]d...> but was:<...b[f]d...>", failure);
    }
    public void testComparisonErrorStartSameComplete() {
    String failure= new ComparisonCompactor(2, "ab", "abc").compact(null);
    assertEquals("expected: but was:", failure);
    }
    public void testComparisonErrorEndSameComplete() {
    String failure= new ComparisonCompactor(0, "bc", "abc").compact(null);
    assertEquals("expected:<[]...> but was:<[a]...>", failure);
    }
    public void testComparisonErrorEndSameCompleteContext() {
    String failure= new ComparisonCompactor(2, "bc", "abc").compact(null);
    assertEquals("expected:<[]bc> but was:<[a]bc>", failure);
    }
    public void testComparisonErrorOverlapingMatches() {
    String failure= new ComparisonCompactor(0, "abc", "abbc").compact(null);
    assertEquals("expected:<...[]...> but was:<...[b]...>", failure);
    }
    public void testComparisonErrorOverlapingMatchesContext() {
    String failure= new ComparisonCompactor(2, "abc", "abbc").compact(null);
    assertEquals("expected: but was:", failure);
    }
    public void testComparisonErrorOverlapingMatches2() {
    String failure= new ComparisonCompactor(0, "abcdde",
    "abcde").compact(null);
    assertEquals("expected:<...[d]...> but was:<...[]...>", failure);
    }
    public void testComparisonErrorOverlapingMatches2Context() {
    String failure= new ComparisonCompactor(2, "abcdde", "abcde").compact(null);
    assertEquals("expected:<...cd[d]e> but was:<...cd[]e>", failure);
    }
    продолжение
    289

    290
    Глава 15 . Внутреннее строение JUnit
    листинг 15 .1 (продолжение)
    public void testComparisonErrorWithActualNull() {
    String failure= new ComparisonCompactor(0, "a", null).compact(null);
    assertEquals("expected: but was:", failure);
    }
    public void testComparisonErrorWithActualNullContext() {
    String failure= new ComparisonCompactor(2, "a", null).compact(null);
    assertEquals("expected:
    but was:", failure);
    }
    public void testComparisonErrorWithExpectedNull() {
    String failure= new ComparisonCompactor(0, null, "a").compact(null);
    assertEquals("expected: but was:
    ", failure);
    }
    public void testComparisonErrorWithExpectedNullContext() {
    String failure= new ComparisonCompactor(2, null, "a").compact(null);
    assertEquals("expected: but was:
    ", failure);
    }
    public void testBug609972() {
    String failure= new ComparisonCompactor(10, "S&P500", "0").compact(null);
    assertEquals("expected:<[S&P50]0> but was:<[]0>", failure);
    }
    }
    Я провел для
    ComparisonCompactor анализ покрытия кода на основе этих тестов .
    В ходе тестирования обеспечивалось 100%-ное покрытие: была выполнена каж- дая строка кода, каждая команда if и цикл for
    . Я удостоверился в том, что код
    работает правильно, а также преисполнился уважения к мастерству его авторов .
    Код
    ComparisonCompactor приведен в листинге 15 .2 . Не жалейте времени и как следует разберитесь в нем . Вероятно, вы согласитесь с тем, что код достаточно выразителен, обладает логичным разбиением и простой структурой . А когда вы закончите, мы вместе начнем придираться к мелочам .
    листинг 15 .2 . ComparisonCompactor.java (исходный код)
    package junit.framework;
    public class ComparisonCompactor {
    private static final String ELLIPSIS = "...";
    private static final String DELTA_END = "]";
    private static final String DELTA_START = "[";
    private int fContextLength;
    private String fExpected;
    private String fActual;
    private int fPrefix;
    private int fSuffix;
    290

    Инфраструктура JUnit
    291
    public ComparisonCompactor(int contextLength,
    String expected,
    String actual) {
    fContextLength = contextLength;
    fExpected = expected;
    fActual = actual;
    }
    public String compact(String message) {
    if (fExpected == null || fActual == null || areStringsEqual())
    return Assert.format(message, fExpected, fActual);
    findCommonPrefix();
    findCommonSuffix();
    String expected = compactString(fExpected);
    String actual = compactString(fActual);
    return Assert.format(message, expected, actual);
    }
    private String compactString(String source) {
    String result = DELTA_START + source.substring(fPrefix, source.length() - fSuffix + 1) + DELTA_END;
    if (fPrefix > 0)
    result = computeCommonPrefix() + result;
    if (fSuffix > 0)
    result = result + computeCommonSuffix();
    return result;
    }
    private void findCommonPrefix() {
    fPrefix = 0;
    int end = Math.min(fExpected.length(), fActual.length());
    for (; fPrefix < end; fPrefix++) {
    if (fExpected.charAt(fPrefix) != fActual.charAt(fPrefix))
    break;
    }
    }
    private void findCommonSuffix() {
    int expectedSuffix = fExpected.length() - 1;
    int actualSuffix = fActual.length() - 1;
    for (; actualSuffix >= fPrefix && expectedSuffix >= fPrefix; actualSuffix--, expectedSuffix--) {
    if (fExpected.charAt(expectedSuffix) != fActual.charAt(actualSuffix))
    break;
    }
    fSuffix = fExpected.length() - expectedSuffix;
    }
    private String computeCommonPrefix() {
    продолжение
    291

    292
    Глава 15 . Внутреннее строение JUnit
    листинг 15 .2 (продолжение)
    return (fPrefix > fContextLength ? ELLIPSIS : "") + fExpected.substring(Math.max(0, fPrefix - fContextLength), fPrefix);
    }
    private String computeCommonSuffix() {
    int end = Math.min(fExpected.length() - fSuffix + 1 + fContextLength, fExpected.length());
    return fExpected.substring(fExpected.length() - fSuffix + 1, end) +
    (fExpected.length() - fSuffix + 1 < fExpected.length() - fContextLength ? ELLIPSIS : "");
    }
    private boolean areStringsEqual() {
    return fExpected.equals(fActual);
    }
    }
    Вероятно, вы найдете в этом модуле некоторые недочеты . В нем встречают- ся длинные выражения, какие-то малопонятные
    +1
    и т . д . Но в целом модуль весьма хорош . В конце концов, он мог бы выглядеть и так, как показано в ли- стинге 15 .3 .
    листинг 15 .3 . ComparisonCompator.java (переработанная версия)
    package junit.framework;
    public class ComparisonCompactor {
    private int ctxt;
    private String s1;
    private String s2;
    private int pfx;
    private int sfx;
    public ComparisonCompactor(int ctxt, String s1, String s2) {
    this.ctxt = ctxt;
    this.s1 = s1;
    this.s2 = s2;
    }
    public String compact(String msg) {
    if (s1 == null || s2 == null || s1.equals(s2))
    return Assert.format(msg, s1, s2);
    pfx = 0;
    for (; pfx < Math.min(s1.length(), s2.length()); pfx++) {
    if (s1.charAt(pfx) != s2.charAt(pfx))
    break;
    }
    int sfx1 = s1.length() - 1;
    int sfx2 = s2.length() - 1;
    292

    Инфраструктура JUnit
    293
    for (; sfx2 >= pfx && sfx1 >= pfx; sfx2--, sfx1--) {
    if (s1.charAt(sfx1) != s2.charAt(sfx2))
    break;
    }
    sfx = s1.length() - sfx1;
    String cmp1 = compactString(s1);
    String cmp2 = compactString(s2);
    return Assert.format(msg, cmp1, cmp2);
    }
    private String compactString(String s) {
    String result =
    "[" + s.substring(pfx, s.length() - sfx + 1) + "]";
    if (pfx > 0)
    result = (pfx > ctxt ? "..." : "") +
    s1.substring(Math.max(0, pfx - ctxt), pfx) + result;
    if (sfx > 0) {
    int end = Math.min(s1.length() - sfx + 1 + ctxt, s1.length());
    result = result + (s1.substring(s1.length() - sfx + 1, end) +
    (s1.length() - sfx + 1 < s1.length() - ctxt ? "..." : ""));
    }
    return result;
    }
    }
    Авторы оставили эту модуль в очень хорошей форме . И все же «правило бой- скаута
    1
    » гласит: все нужно оставлять чище, чем было до вашего прихода . Итак, как же улучшить исходный код в листинге 15 .2?
    Первое, что мне решительно не понравилось, — префикс f
    у имен переменных классов [N6] . В современных средах разработки подобное кодирование области видимости излишне . Давайте уберем все префиксы:
    private int contextLength;
    private String expected;
    private String actual;
    private int prefix;
    private int suffix;
    Также бросается в глаза неинкапсулированная условная команда в начале функ- ции compact
    [G28] .
    public String compact(String message) {
    1   ...   28   29   30   31   32   33   34   35   ...   49


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