← Назад к обзору

Спагетти-код

Вообще-то это группы объектов, написанные для конкретного бизнес-процесса, но не предназначенные для использования в другом. На практике это скопипащенные куски кода, которые в плюс-минус одинаковом виде встречаются раз так 5–10 минимум в разных модулях.

Симптомы

  • Хаотичный поток управления: много goto, глубокие вложенности, выходы из середины.
  • Один и тот же фрагмент встречается в нескольких местах с мелкими правками.
  • Изменение в одном месте требует ручных правок в пяти других.

Пример

Пример спагетти-кода из Linux SCSI driver (с небольшими изменениями) начала нулевых:

wait_nomsg:
        if ((inb(tmport) & 0x04) != 0) {
                goto wait_nomsg;
        }
        outb(1, 0x80);
        udelay(100);
        for (n = 0; n < 0x30000; n++) {
                if ((inb(tmport) & 0x80) != 0) {        /* bsy ? */
                        goto wait_io;
                }
        }
        goto TCM_SYNC;
wait_io:
        for (n = 0; n < 0x30000; n++) {
                if ((inb(tmport) & 0x81) == 0x0081) {
                        goto wait_io1;
                }
        }
        goto TCM_SYNC;
wait_io1:
        inb(0x80);
        val |= 0x8003;          /* io,cd,db7  */
        outw(val, tmport);
        inb(0x80);
        val &= 0x00bf;          /* no sel     */
        outw(val, tmport);
        outb(2, 0x80);
TCM_SYNC:
/* ... */
small_id:
        m = 1;
        m <<= k;
        if ((m & assignid_map) == 0) {
                goto G2Q_QUIN;
        }
        if (k > 0) {
                k--;
                goto small_id;
        }
G2Q5:                   /* srch from max acceptable ID#  */
        k = i;                  /* max acceptable ID#            */
G2Q_LP:
        m = 1;
        m <<= k;
        if ((m & assignid_map) == 0) {
                goto G2Q_QUIN;
        }
        if (k > 0) {
                k--;
                goto G2Q_LP;
        }
G2Q_QUIN:   /* k=binID#,       */

«Хороший» вариант: то же самое, но с явными функциями и без goto:

static int wait_ready(unsigned int port) {
    for (int n = 0; n < 0x30000; n++) {
        if ((inb(port) & 0x80) != 0)      /* bsy ? */
            return wait_io_done(port);
    }
    return TCM_SYNC;
}

Почему возникает

Что надо знать: хотя бы принципы функционального программирования. А в идеале — ООП и архитектурные принципы типа DDD.

Это тот самый код, который имеет очень яркую региональную принадлежность, и если вы аутсорсите, например, в Индию, то встречаться с ним придётся довольно часто.

Как исправить

  • Разбивать большие функции на маленькие с одним уровнем абстракции.
  • Устранять goto и глубокие вложенности.
  • Выделять повторяющиеся фрагменты в функции и методы.
  • Добавлять характризационные тесты до рефакторинга.