Эволюционирующая архитектура и спонтанное проектирование: Составной метод и SLAP

Как обнаружить скрытую архитектуру в уже существующем коде? В этой статье обсуждаются два важных шаблона для структурирования кода: составной метод (composed method) и SLAP (single level of abstraction - единый уровень абстракции). Применяя к коду эти принципы, можно обнаружить повторяющиеся фрагменты, которые были не видны до этого, и в дальнейшем абстрагировать существующий код до полученной в ходе анализа структуры.

Нил Форд, Архитектор приложений, ThoughtWorks

Нил Форд (Neal Ford) работает архитектором приложений в ThoughtWorks, революционной компании, предоставляющей профессиональные IT-услуги и помогающей талантливым людям по всему миру эффективнее использовать программное обеспечение. Он также является проектировщиком и разработчиком приложений, учебных материалов, журнальных статей, учебных курсов, видео/DVD-презентаций и автором книг "Разработка с Delphi: Объектно-ориентированный подход", "JBuilder 3 Unleashed" и "Искусство разработки Web-приложений на Java". Он специализируется на консультациях по построению широкомасштабных корпоративных приложений. Он также является общепризнанным докладчиком и выступал на многочисленных конференциях разработчиков по всему миру. С ним можно связаться по адресу: nford@thoughtworks.com.



27.01.2012

В двух предыдущих статьях этой серии обсуждалось, как использование методологии разработки, основанной на тестах (TDD), помогает раскрывать дизайн по мере разработки проекта. Это отлично работает, если проект начинается с нуля. Но как быть в часто возникающей ситуации, когда уже имеется большое количество кода, который к тому же не является лучшим в мире? Как найти в коде повторяющиеся фрагменты и скрытую архитектуру?

Об этой серии

Цель этой серии - показать новые точки зрения на часто обсуждаемые, но до сих пор не решённые проблемы в области архитектуры и проектирования ПО. На конкретных примерах Нил Форд поможет приобрести базовые знания по гибким (agile) методикам: эволюционирующая архитектура (evolutionary architecture) и спонтанное проектирование (emergent design). Отложив принятие важных решений об архитектуре и проектировании ПО до последнего момента, можно избежать ненужной сложности, оказывающей негативное влияние на проект.

В этой статье рассказывается о двух шаблонах двадцатилетней давности, которые помогают реорганизовать код для обнаружения повторяющихся фрагментов: составной метод и принцип единого уровня абстракции (SLAP). Элементы хорошей архитектуры уже имеются в коде; просто необходимы инструменты, чтобы помочь выявить скрытые достоинства.

Составной метод

Один из нежелательных побочных эффектов технологических изменений заключается в том, что разработчики часто игнорируют знания, накопленные за годы развития программирования. Принято думать, что всё написанное раньше, чем в последние несколько лет, безнадёжно устарело. Это, конечно же, неправда: множество книг содержат знания, крайне важные для программистов. Одна из таких классических книг (сейчас в основном игнорируемая) - это Smalltalk Best Practice Patterns, написанная Кентом Беком (Kent Beck) (см. раздел Ресурсы). Любой Java-программист может задать вопрос "Чем мне может помочь книга 13-летней давности о Smalltalk?". Однако оказывается, что Smalltalk-программисты были первыми, кто программировал на объектно-ориентированном языке, и они наработали много хороших идей. Одна из таких идей - это составной метод.

Шаблон составной метод определяется тремя ключевыми понятиями:

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

Составной метод уже обсуждался в статье Test-driven design, Part 1 в контексте написания unit-тестов до написания самого кода. При строгом следовании TDD естественным образом создаются методы, которые соответствуют этому шаблону. Но как быть с уже существующим кодом? Настало время разобраться, как использовать шаблон составной метод для поиска скрытой архитектуры.

Идиоматические шаблоны

Многие наверняка знакомы с официальной версией шаблонов проектирования, опубликованной в исходной книге Design Patterns группы авторов, известной как "Банда четырех" (Gang of Four, см. раздел Ресурсы)). Она описывает основные шаблоны, которые применимы ко всем проектам. Однако любое решение содержит идиоматические шаблоны, которые хотя и недостаточно формальны для описания в книге, но тем не менее широко распространены. Идиоматические шаблоны представляют общие решения, применяемые при проектировании кода. Весь смысл спонтанного проектирования состоит в обнаружении этих шаблонов. Они бывают как чисто техническими (например, способ управления транзакциями, используемый в проекте), так и специализированными для данной предметной области (например, "всегда проверять кредитный уровень покупателя, прежде чем отгружать товар").

Рефакторинг для перехода к составным методам

Рассмотрим простой метод в листинге 1, предназначенный для использования низкоуровневого JDBC-подключения для соединения с базой данных, сбора объектов типа Part и помещения их в коллекцию типа List:

Листинг 1. Простой метод для собирания объектов Part
public void populate() throws Exception  {
    Connection c = null;
    try {
        Class.forName(DRIVER_CLASS);
        c = DriverManager.getConnection(DB_URL, USER, PASSWORD);
        Statement stmt = c.createStatement();
        ResultSet rs = stmt.executeQuery(SQL_SELECT_PARTS);
        while (rs.next()) {
            Part p = new Part();
            p.setName(rs.getString("name"));
            p.setBrand(rs.getString("brand"));
            p.setRetailPrice(rs.getDouble("retail_price"));
            partList.add(p);
        }    
    } finally {
        c.close();
    }
}

Термин Olio переводится как "коллекция разнообразных вещей" и используется в разговорном языке как синоним слова leftover (остаток). (Это слово часто встречается в кроссвордах.) Olio-методы - это гигантские методы с большим набором разнообразной функциональности, затрагивающие всю предметную область. Методы с длиной, превышающей 300 строк, по определению являются olio-методами. Как такой метод может быть цельным, если он такой большой? Olio-методы являются одним из факторов, замедляющих рефакторинг, тестирование и проектирование.

В листинге 1 не содержится ничего сложного. В нем, очевидно, также не содержится кода, который можно было бы использовать повторно. Это довольно короткий метод, но его также можно преобразовать. Шаблон "составной метод" гласит, что каждый метод должен делать одну и только одну вещь, а наш метод нарушает это правило. Для Java-проектов существует эвристическое правило, что любой метод длиннее 10 строк скорее всего требует переработки, потому что, возможно, он делает более одного действия. Переделаем этот метод с учётом требований шаблона "составной метод", чтобы увидеть, как можно выделить элементарные составляющие. Переработанная версия представлена в листинге 2:

Листинг 2. Переделанный метод populate()
public void populate() throws Exception {
    Connection c = null;
    try {
        c = getDatabaseConnection();
        ResultSet rs = createResultSet(c);
        while (rs.next())
            addPartToListFromResultSet(rs);
    } finally {
        c.close();
    }
}

private ResultSet createResultSet(Connection c)
        throws SQLException {
    return c.createStatement().
            executeQuery(SQL_SELECT_PARTS);
}

private Connection getDatabaseConnection()
        throws ClassNotFoundException, SQLException {
    Connection c;
    Class.forName(DRIVER_CLASS);
    c = DriverManager.getConnection(DB_URL,
            "webuser", "webpass");
    return c;
}

private void addPartToListFromResultSet(ResultSet rs)
        throws SQLException {
    Part p = new Part();
    p.setName(rs.getString("name"));
    p.setBrand(rs.getString("brand"));
    p.setRetailPrice(rs.getDouble("retail_price"));
    partList.add(p);
}

Метод populate() теперь значительно короче и читается как краткое описание задачи, которую необходимо выполнить, а сама реализация задачи находится в private-методах. Как только были выделены элементарные части, можно посмотреть, какие артефакты вообще имеются в методе. Сразу становится видно, что метод getDatabaseConnection() ничего не делает с объектами part - это общая функциональность для подключения к базе данных. Отсюда можно сделать вывод, что этому методу не место в данном классе, поэтому можно перенести его в класс BoundaryBase, который является родительским классом для PartDb.

Есть ли в листинге 2 другие методы, которые можно было бы перенести в родительский класс? Метод createResultSet() выглядит довольно общим, но он содержит ссылку на объекты part, а именно на константу SQL_SELECT_PARTS. Если найти способ заставить дочерний класс (PartDb) передать в родительский класс значение этого SQL-запроса, то этот метод также можно переместить на уровень выше. В этом как раз и заключается идея шаблона проектирования "абстрактный метод". В этом случае метод createResultSet() перемещается в класс BoundaryBase вместе с сопутствующим абстрактным методом getSqlForEntity(), как показано в листинге 3:

Листинг 3. Класс BoundaryBase
abstract public class BoundaryBase {
    private static final String DRIVER_CLASS =
            "com.mysql.jdbc.Driver";
    private static final String DB_URL =
            "jdbc:mysql://localhost/orderentry";

    protected Connection getDatabaseConnection() throws ClassNotFoundException,
            SQLException {
        Connection c;
        Class.forName(DRIVER_CLASS);
        c = DriverManager.getConnection(DB_URL, "webuser", "webpass");
        return c;
    }                               

    abstract protected String getSqlForEntity();

    protected ResultSet createResultSet(Connection c) throws SQLException {
        Statement stmt = c.createStatement();
        return stmt.executeQuery(getSqlForEntity());
    }

Это интересно, но какие еще методы можно перенести из дочернего класса в родительский? Если посмотреть на метод populate() в листинге 2, то к классу PartDb его привязывают методы getDatabaseConnection(), createResultSet() и addPartToListFromResultSet(). Первые два метода уже перемещены в родительский класс. Если сделать абстрактным метод addPartToListFromResultSet() (при этом желательно изменить название на более подходящее), то можно полностью переместить метод populate() в родительский класс, как показано в листинге 4:

Листинг 4. Класс BoundaryBase
abstract public class BoundaryBase {
    private static final String DRIVER_CLASS =
            "com.mysql.jdbc.Driver";
    private static final String DB_URL =
            "jdbc:mysql://localhost/orderentry";

    protected Connection getDatabaseConnection() throws ClassNotFoundException,
            SQLException {
        Connection c;
        Class.forName(DRIVER_CLASS);
        c = DriverManager.getConnection(DB_URL, "webuser", "webpass");
        return c;
    }                               

    abstract protected String getSqlForEntity();

    protected ResultSet createResultSet(Connection c) throws SQLException {
        Statement stmt = c.createStatement();
        return stmt.executeQuery(getSqlForEntity());
    }

    abstract protected void addEntityToListFromResultSet(ResultSet rs)
            throws SQLException;

    public void populate() throws Exception {
        Connection c = null;
        try {
            c = getDatabaseConnection();
            ResultSet rs = createResultSet(c);
            while (rs.next())
                addEntityToListFromResultSet(rs);
        } finally {
            c.close();
        }
    }

}

После перемещения всех этих методов в родительский класс содержание класса PartDb существенно упрощается, как показано в листинге 5:

Листинг 5. Упрощённый и переделанный класс PartDb
public class PartDb extends BoundaryBase {
    private static final int DEFAULT_INITIAL_LIST_SIZE = 40;
    private static final String SQL_SELECT_PARTS =
            "select name, brand, retail_price from parts";
    private static final Part[] TEMPLATE = new Part[0];
    private ArrayList partList;

    public PartDb() {
        partList = new ArrayList(DEFAULT_INITIAL_LIST_SIZE);
    }

    public Part[] getParts() {
        return (Part[]) partList.toArray(TEMPLATE);
    }

    protected String getSqlForEntity() {
        return SQL_SELECT_PARTS;
    }

    protected void addEntityToListFromResultSet(ResultSet rs) 
            throws SQLException {
        Part p = new Part();
        p.setName(rs.getString("name"));
        p.setBrand(rs.getString("brand"));
        p.setRetailPrice(rs.getDouble("retail_price"));
        partList.add(p);
    }
}

Какие цели были достигнуты после выполнения этого упражнения по переделыванию кода? Во-первых, теперь у нас есть два класса, которые более приспособлены для своих целей, чем ранее. Все методы в обоих классах теперь совсем небольшие, что облегчает их понимание. Во-вторых, следует заметить, что класс PartDb имеет дело только с объектами part и ничем больше. Весь общий код и код для подключения к базе данных перемещён в родительский класс. В-третьих, все эти методы теперь можно тестировать: каждый метод (за исключением populate()) выполняет только одно действие. Метод populate() является управляющим методом этих классов. Он использует другие private-методы для своей работы, а сам выглядит как описание последовательности выполняемых действий. В-четвёртых, теперь, когда имеются маленькие строительные блоки, повторное использование методов стало гораздо проще, потому что теперь можно соединять и комбинировать их как угодно. Шансов на повторное использование такого большого метода, как исходный метод populate(), немного: маловероятно, что в другом классе понадобится выполнить те же самые действия в том же самом порядке. А с помощью элементарных методов можно комбинировать и добавлять различные функции.

Тенденция такова, что самые лучшие инфраструктуры извлекаются из уже существующего рабочего кода, а не проектируются специально с нуля. При создании инфраструктуры "из головы" необходимо учитывать все способы, которыми разработчики могут захотеть использовать ее. Это приводит к тому, что инфраструктура содержит множество возможностей, которыми с большой степенью вероятности никто не будет пользоваться. Однако все равно приходится принимать во внимание даже неиспользуемые возможности инфраструктуры, потому что они добавляют в приложение дополнительную сложность; например, реализовать какую-то возможность можно, просто добавив дополнительный элемент в конфигурационный файл, или это потребует изменить способ реализации функциональности. Специально спроектированные инфраструктуры обычно содержат огромный набор различных возможностей, в то время как остальные (непредусмотренные) возможности остаются за бортом. JavaServer Faces (JSF) - классический пример специально спроектированной инфраструктуры. Одна из ее полезных возможностей - это способность подключать различные потоки вывода, если в результате надо получить формат, отличный от HTML. Несмотря на то, что эта возможность используется достаточно редко, все пользователи JSF должны понимать её влияние на жизненный цикл запроса JSF.

Инфраструктуры, которые вырастают из работающих приложений, обычно предлагают более прагматичный набор возможностей из-за того, что они решают реальные проблемы, с которыми кто-то когда-то столкнулся в процессе написания приложения. Такие инфраструктуры обычно содержат меньше посторонних возможностей. Для примера можно сравнить специально написанную инфраструктуру JSF с инфраструктурой Ruby on Rails, которая была получена в результате работы с реальным кодом.

Главная польза этого упражнения - это возможность обнаружить повторяющийся код. Если посмотреть на код в листинге 1, в нем невозможно выделить повторяющиеся фрагменты; виден только сплошной поток кода. Повторяющиеся фрагменты можно выделить путем разделения olio-метода. Но полученная выгода этим не ограничивается. Также мы создали основу для простой инфраструктуры, отвечающей за персистентность данных в приложении. Когда потребуется создать следующий простой класс для извлечения объектов из базы, находящийся на границе базы данных и приложения, у нас уже будет код, который поможет в решении этой задачи. Это и есть преимущество извлечения инфраструктур из существующего кода перед их изобретением с нуля.

Сбор повторяющихся фрагментов кода по всему приложению позволяет лучше рассмотреть общую архитектуру приложения в массиве кода, который составляет приложение. Одна из целей спонтанного проектирования - это обнаружение идиоматических шаблонов, используемых в приложении. Комбинация классов BoundaryBase и PartDb образует полезный шаблон, который применяется по всему приложению. Когда все части разделены на маленькие фрагменты, становится проще разобраться, как они соединяются между собой.


SLAP

Вторая часть определения шаблона "составной метод" гласит: "все операции в методе необходимо размещать на одном уровне абстракции". Пример применения этого принципа поможет понять, что это значит и какое влияние этот принцип может оказать на архитектуру.

Рассмотрим код из листинга 6, взятый из небольшого приложения для электронной торговли. Метод addOrder() принимает на вход несколько параметров и помещает информацию о заказе в базу данных.

Листинг 6. Метод addOrder() с Web-сайта электронной торговли
public void addOrder(ShoppingCart cart, String userName,
                     Order order) throws SQLException {
    Connection c = null;
    PreparedStatement ps = null;
    Statement s = null;
    ResultSet rs = null;
    boolean transactionState = false;
    try {
        s = c.createStatement();
        transactionState = c.getAutoCommit();
        int userKey = getUserKey(userName, c, ps, rs);
        c.setAutoCommit(false);
        addSingleOrder(order, c, ps, userKey);
        int orderKey = getOrderKey(s, rs);
        addLineItems(cart, c, orderKey);
        c.commit();
        order.setOrderKeyFrom(orderKey);
    } catch (SQLException sqlx) {
        s = c.createStatement();
        c.rollback();
        throw sqlx;
    } finally {
        try {
            c.setAutoCommit(transactionState);
            dbPool.release(c);
            if (s != null)
                s.close();
            if (ps != null)
                ps.close();
            if (rs != null)
                rs.close();
        } catch (SQLException ignored) {
        }
    }
}

Как видно, внутренняя структура метода addOrder() крайне запутанна. В данном случае интерес представляет код в начале блока try. Следует обратить внимание на следующие строки:

c.setAutoCommit(false);
addSingleOrder(order, c, ps, userKey);

Эти две строки демонстрируют нарушение принципа SLAP. Вызов метода в первой строке (и все вызовы над ним) выполняют низкоуровневые задачи по настройке инфраструктуры базы данных. Во второй строке вызывается метод более высокого порядка, на котором обычно работают бизнес-аналитики. Эти две строки пришли из разных миров. Крайне сложно читать код, если мысленно необходимо переключаться между уровнями абстракции, и это как раз то, чего принцип SLAP старается избежать. Если код тяжело воспринимается, то в дальнейшем будет сложно понять архитектуру, лежащую в его основе, и какие задачи он выполняет, что затрудняет выделение идиоматических шаблонов для данного приложения.

Для улучшения кода из листинга 6 можно переделать его с учётом принципа SLAP. После пары циклов переработки с помощью приема рефакторинга "извлечение метода" можно получить код, похожий на листинг 7:

Листинг 7. Метод addOrder() с улучшенным уровнем абстракции
public void addOrderFrom(ShoppingCart cart, String userName,
                     Order order) throws SQLException {
    setupDataInfrastructure();
    try {
        add(order, userKeyBasedOn(userName));
        addLineItemsFrom(cart, order.getOrderKey());
        completeTransaction();
    } catch (SQLException sqlx) {
        rollbackTransaction();
        throw sqlx;
    } finally {
        cleanUp();
    }
}

private void setupDataInfrastructure() throws SQLException {
    _db = new HashMap();
    Connection c = dbPool.getConnection();
    _db.put("connection", c);
    _db.put("transaction state",
            Boolean.valueOf(setupTransactionStateFor(c)));
}

private void cleanUp() throws SQLException {
    Connection connection = (Connection) _db.get("connection");
    boolean transactionState = ((Boolean)
            _db.get("transation state")).booleanValue();
    Statement s = (Statement) _db.get("statement");
    PreparedStatement ps = (PreparedStatement)
            _db.get("prepared statement");
    ResultSet rs = (ResultSet) _db.get("result set");
    connection.setAutoCommit(transactionState);
    dbPool.release(connection);
    if (s != null)
        s.close();
    if (ps != null)
        ps.close();
    if (rs != null)
        rs.close();
}

private void rollbackTransaction()
        throws SQLException {
    ((Connection) _db.get("connection")).rollback();
}

private void completeTransaction()
        throws SQLException {
    ((Connection) _db.get("connection")).commit();
}

private boolean setupTransactionStateFor(Connection c)
        throws SQLException {
    boolean transactionState = c.getAutoCommit();
    c.setAutoCommit(false);
    return transactionState;
}

Теперь метод выглядит гораздо более читабельным. Его основное тело придерживается шаблона "составной метод": оно выглядит как последовательность действий, которые необходимо выполнить. Методы теперь находятся на таком высоком уровне абстракции, что их можно показать даже не техническому персоналу, чтобы объяснить, что этот метод делает. Если рассмотреть метод completeTransaction(), то можно заметить, что в нём только одна строка кода. Можно ли поместить эту строку обратно в метод addOrder()? Нет, так как это повредит читабельности кода и уровню абстракции. Переход с высокоуровневого описания бизнес-процесса на мельчайшие подробности обработки транзакций нарушает принцип SLAP. Наличие метода completeTransaction() переводит код на более концептуальный уровень, отдаляясь от конкретных деталей реализации. Если в будущем потребуется поменять способ доступа к базе данных, то можно будет изменить только содержимое метода completeTransaction(), не трогая кода, который вызывает этот метод.

Главная цель SLAP - сделать код более удобным для чтения и восприятия. Но он также помогает обнаружить идиоматические шаблоны, существующие в коде. Следует отметить, что один такой шаблон можно обнаружить в способе, котором транзакции защищают процесс обновления данных в СУБД. Можно еще раз изменить метод addOrder(), чтобы перейти к сочетанию методов, показанных в листинге 8:

Листинг 8. Транзакционный шаблон доступа
public void wrapInTransaction(Command c) throws SQLException {
    setupDataInfrastructure();
    try {
        c.execute();
        completeTransaction();
    } catch (RuntimeException ex) {
        rollbackTransaction();
        throw ex;
    } finally {
        cleanUp();
    }
}

public void addOrderFrom(final ShoppingCart cart, final String userName,
                         final Order order) throws SQLException {
    wrapInTransaction(new Command() {
        public void execute() throws SQLException{
            add(order, userKeyBasedOn(userName));
            addLineItemsFrom(cart, order.getOrderKey());
        }
    });                
}

В приложение был добавлен метод wrapInTransaction(), реализующий этот общий шаблон с помощью шаблона проектирования команда (command) из книги "банды четырех" (см. раздел Ресурсы)). Метод wrapInTransaction() выполняет работу, необходимую для того, чтобы убедиться, что код работает правильно. При этом получается немного "корявого" шаблонного кода из-за анонимного внутреннего класса, который определяет реальное назначение этого метода, - это две строки кода в теле метода addOrderFrom(). Этот блок для защиты ресурсов может появляться в коде неоднократно, так что это подходящий кандидат для перемещения вверх по иерархии наследования.

Причина, по которой метод wrapInTransaction() был реализован с использованием анонимного внутреннего класса, подчёркивает важный аспект, связанный с выразительностью синтаксиса языка. Если писать этот код с помощью Groovy, то можно использовать блоки-замыкания (блоки кода, используемые в языке Groovy) чтобы получить более наглядный и понятный код, обеспечивающий такую же функциональность, как показано в листинге 9:

Листинг 9. Защита доступа к базе данных с помощью транзакций, реализованная с использованием блоков-замыканий Groovy
public class OrderDbClosure {
   def wrapInTransaction(command) {
     setupDataInfrastructure()
     try {
       command()
       completeTransaction()
     } catch (RuntimeException ex) {
       rollbackTransaction()
       throw ex
     } finally {
       cleanUp()
     }
   }
   
   def addOrderFrom(cart, userName, order) {
     wrapInTransaction {
       add order, userKeyBasedOn(userName)
       addLineItemsFrom cart, order.getOrderKey()
     }
   }
}

Продвинутый синтаксис и возможности Groovy (см. раздел Ресурсы)) позволяют писать с его помощью более читабельный код, особенно когда при этом применяются правила "составного метода" и SLAP.


Заключение

В этой части были рассмотрены два шаблона, важных для архитектуры и читабельности кода. Первый шаг в устранении плохой архитектуры в существующем приложении состоит в том, чтобы перевести его в форму, с которой можно работать. Метод из 300 строк бесполезен с точки зрения проектирования или повторного использования, так как в нем нельзя выделить важнейшие составные части. Разделение метода на элементарные фрагменты позволило увидеть, какие артефакты имеются в коде. Как только повторяющиеся фрагменты выделены, их можно извлечь и применить к ним принципы идиоматических шаблонов.

В следующей статье будет рассматриваться преобразование кода, направленное на построение архитектуры и основанное на принципах "составного метода" и SLAP. Также будет показано, как обнаружить проектные решения, уже скрытые в коде.

Ресурсы

  • Evolutionary architecture and emergent design: Composed method and SLAP: оригинал статьи (EN).
  • Smalltalk Best Practice Patterns (Kent Beck, Prentice Hall, 1996) (EN): дополнительная информация о шаблоне составной метод.
  • The Productive Programmer (Neal Ford, O'Reilly Media, 2008) (EN): более подробный пример использования составного метода и SLAP приведен в части 2 этой книги.
  • Design Patterns (Erich Gamma et al., Addison-Wesley, 1995) (EN): классическая книга о шаблонах проектирования, включая шаблон "команда".
  • Spring Framework: инфраструктура Spring - хороший пример инфраструктуры, извлеченной из существующего кода.
  • Groovy: A DSL for Java programmers (Scott Davis, developerWorks, февраль 2009 г.) (EN): серия статей Practically Groovy, знакомящая с возможностями синтаксиса Groovy, которые позволяют писать более компактный и удобный для восприятия код.

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=789925
ArticleTitle=Эволюционирующая архитектура и спонтанное проектирование: Составной метод и SLAP
publish-date=01272012