Модифицируемость кода (Changeability QA)

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

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

http://olimex.files.wordpress.com/2013/09/linux-versus-windows-platform.jpg

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

Чисто умозрительно представим весь код программы в виде таблицы в базе данных, тогда для его модификации нам бы потребовался язык манипулирования данными (Data Manipulation Language). Функции языков DML определяются первым словом в предложении (часто называемом запросом), которое почти всегда является глаголом. В случае с SQL эти глаголы — выбрать (select), вставить (insert), обновить (update), и удалить (delete).

Для примера простой код на C++:
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    printf("Hello World!\n");
    return EXIT_SUCCESS;
}


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

Деление по структуре кода могло бы иметь следующий вид:
// begin: заголовочные файлы
#include <stdlib.h>
#include <stdio.h>
// end: заголовочные файлы

// begin: точка входа в приложение
int main(int argc, char* argv[])
{
    printf("Hello World!\n");
    return EXIT_SUCCESS;
}
// end: точка входа в приложение


Ключевой особенностью группировки являются синтаксические конструкции. В классе это могут быть закрытые поля, искусственное выделение свойств и методов. Главное, что для конкретной программы подобное разделение не уникально.

А вот функционал одновременно является и уникальным и не уникальным. Его уникальность в том, что он часто содержится в программе в единственном числе, нет смысла плодить китайский код. Не уникальность же позволяет переносить его между различными программами, в том числе добавлять чужие решения в свой код.

Предположим есть некий функционал:
1. ...
2. …
3. поздороваться с миром
n. …

Где 1…n уникальные идентификаторы функционала. Буквы обозначают b — begin (начало секции), e — end (конец секции), c — continue (секция до конца строки).

Вставили новый функционал (insert):
#include <stdlib.h>
#include <stdio.h> // 3.c

int main(int argc, char* argv[])
{
    printf("Hello World!\n"); // 3.c
    return EXIT_SUCCESS;
}


Обновили текущий функционал (update):
#include <stdlib.h>
#include <stdio.h> // 3.c

// 3.b
inline void hello_world()
{
    printf("Hello World!\n");
}
// 3.e

int main(int argc, char* argv[])
{
    hello_world(); // 3.c
    return EXIT_SUCCESS;
}


Удалили текущий функционал (delete):
#include <stdlib.h>

int main(int argc, char* argv[])
{
    return EXIT_SUCCESS;
}


Выделение нового функционала (select) могло бы осуществляться простым поиском по файлам проекта "// 3.".

Конечно, всё это условность и можно использовать более совершенные методы отвечающие за возможность модифицируемости. В статье же хотелось показать сам принцип. Произведя операцию удаления функционала под номером 3 после вставки или после обновления получаем абсолютно одинаковые результаты. Та же директива включения stdlib.h нужна была лишь для использования EXIT_SUCCESS.

Текущий список функционала программы:
1. запустить приложение
2. выйти из приложения
3. поздороваться с миром

Код с маркерами изменения функционала:
#include <stdlib.h> // 2.c
#include <stdio.h> // 3.c

// 3.b
inline void hello_world()
{
    printf("Hello World!\n");
}
// 3.e

// 1.b
int main(int argc, char* argv[])
{
    hello_world(); // 3.c
    return EXIT_SUCCESS; // 2.c
}
// 1.e


Или вариант не меняющий количество строк кода. Так же не мешает однострочным комментариям и самодокументируемому коду. Применительно к C++ не прерывает многострочные комментарии (/*..*/):
#include <stdlib.h> // 2.c
#include <stdio.h> // 3.c

inline void hello_world() // 3.b
{
    printf("Hello World!\n");
} // 3.e

int main(int argc, char* argv[]) // 1.b
{
    hello_world(); // 3.c
    return EXIT_SUCCESS; // 2.c
} // 1.e