Осуществляет рефакторинг ошибками компиляции плохо?

В этом примере я собираюсь использовать библиотеку Express HTTP

const express = require('express');
const app = express();
const { exec } = require('child_process');

const AuthValidator = (req, res, next) => {
  if(req.headers['X-Secret-Code'] === 'some_super_secret_password') return next();
  
  return res.status(400).end('Bad Authorization');
  
}

app.get('/api/startservice', (req, res) => {
  exec('screen && node start.js && node index.js', (error, stdout) => {
    if (error) {
      console.error(`Fail to start Node app: ${error}`);
      return res.status(500).end(error.message);
    }
    res.status(201).end(stdout);
  });
});

app.get('/api/stopservice', (req, res) => {
  exec('node stop.js', (error, stdout) => {
    if (error) {
      console.error(`Fail to stop Node app: ${error}`);
      return res.status(500).end(error.message);
    }
    res.status(201).end(stdout);
  });
});

И как только клиент выполнит GET запрос к конечной точке /api/startservice, приложение Node поможет выполнить три из этих команд

То же самое касается /api/stopservice, как только эта конечная точка получает триггер, приложение Node выполнит stop.js сценарий

10
задан user 13 May 2012 в 05:49
поделиться

13 ответов

Это - общая и полезная техника для статически скомпилированных языков. Общая версия того, что Вы делаете, могла быть указана следующим образом:

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

Существует множество заключений:

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

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

  • Если Вы работаете на языке с перегрузкой, только измените один вариант или добавьте новый вариант. Вы рискуете иметь перегружающуюся твердость тихо по-другому. При использовании перегрузки довольно трудно заставить компилятор работать на Вас в способе, которым Вы надеетесь. Единственным путем я знаю об обработать перегрузку, должен рассуждать глобально обо всем использовании. Если Ваш IDE не поможет Вам, необходимо изменить имена всех перегруженных вариантов. Неприятный.

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

9
ответ дан 3 December 2019 в 13:32
поделиться

Я никогда не полагаюсь на простую компиляцию при рефакторинге, код может скомпилировать, но ошибки, возможно, были представлены.

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

Я не говорю, переходят к Разработке через тестирование, просто пишут модульные тесты для получения необходимой уверенности, что необходимо осуществить рефакторинг.

10
ответ дан 3 December 2019 в 13:32
поделиться

Я не вижу проблемы с ним. Это безопасно, и, пока Вы не фиксируете изменений перед компиляцией это не имеет никакого долгосрочного эффекта. Кроме того, Resharper и VS имеют инструменты, которые делают процесс немного легче для Вас.

Вы используете подобный процесс в другом направлении в TDD - Вы пишете код, которому нельзя было определить методы, который заставляет это не компилировать, затем Вы пишете достаточно кода для компиляции (затем проходят тесты, и так далее...),

5
ответ дан 3 December 2019 в 13:32
поделиться

Когда Вы готовы прочитать книги по предмету, я рекомендую Michael Feather, "Работающему Эффективно с Унаследованным кодом". (Добавленный неавтором: также классическая книга Fowler "Рефакторинг" - и веб-сайт Рефакторинга может быть полезной.)

Он говорит о identifiying характеристики кода, Вы работаете перед внесением изменения и выполнения, что он называет рефакторингом царапины. Это - refectoring для нахождения характеристик кода и затем выбрасывания результатов.

То, что Вы делаете, использует компилятор в качестве автотеста. Это протестирует тот Ваш код компиляции, но не, если поведение изменилось из-за Вашего рефакторинга или если было какое-либо влияние стороны.

Рассмотрите это

class myClass {
     void megaMethod() 
     {
         int x,y,z;
         //lots of lines of code
         z = mysideEffect(x)+y;
         //lots more lines of code 
         a = b + c;
     }
}

Вы могли осуществить рефакторинг addtion

class myClass {
     void megaMethod() 
     {
         int a,b,c,x,y,z;
         //lots of lines of code
         z = addition(x,y);
         //lots more lines of code
         a = addition(b,c);  
     }

     int addition(int a, b)
     {
          return mysideaffect(a)+b;
     }
}

и это работало бы, но второе дополнение будет неправильным, поскольку оно вызвало метод. Дальнейшие тесты были бы необходимы кроме просто компиляции.

5
ответ дан 3 December 2019 в 13:32
поделиться

Я был бы точно так же, как, чтобы добавить ко всей мудрости здесь, что существует еще один случай, где это не могло бы быть безопасно. Отражение. Это влияет на среды как.NET и Java (и другие также, конечно). Ваш код скомпилировал бы, но все еще будут ошибки периода выполнения, когда Отражение попробовало бы доступ несуществующие переменные. Например, это могло быть довольно распространено при использовании ORM, любят, в спящем режиме и забывают обновлять XML-файлы отображения.

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

Я не думаю, что существует 100%-й надежный метод хотя кроме прохождения через всего кода вручную.

3
ответ дан 3 December 2019 в 13:32
поделиться

Это - довольно распространенный способ, которым я думаю, поскольку это находит все ссылки на ту определенную вещь. Однако современные IDE, такие как Visual Studio имеют функцию Find All References, которая делает это ненужным.

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

2
ответ дан 3 December 2019 в 13:32
поделиться

Достаточно легко думать о примеры, где рефакторинг ошибками компилятора перестал работать тихо и приводит к непреднамеренным результатам.
Несколько случаев, которые приходят на ум: (Я предположу, что мы говорим о C++),

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

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

4
ответ дан 3 December 2019 в 13:32
поделиться

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

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

http://www.refactoring.com/

2
ответ дан 3 December 2019 в 13:32
поделиться

Другая возможность, которую Вы могли рассмотреть при использовании одного из dotnet lanuages состоит в том, чтобы отметить "старый" метод с помощью Устаревшего атрибута, который представит все предупреждения компилятора, но все еще уедет, вызываемый код должен там быть кодом вне Вас управление (если Вы пишете API, или если Вы не используете опцию, строгую в VB.Net, например). Вы можете, чем весело осуществлять рефакторинг, имение устаревшей версии называет новую; как пример:

    public string Username
    {
        get
        {
            return this.userField;
        }
        set
        {
            this.userField = value;
        }
    }

    public int Login()
    {
        /* do stuff */
    }

Становится:

    [ObsoleteAttribute()]
    public string Username
    {
        get
        {
            return this.userField;
        }
        set
        {
            this.userField = value;
        }
    }

    [ObsoleteAttribute("Replaced by Login(username, password)")]
    public int Login()
    {
        Login(Username, Pasword);
    }

    public int Login(string username, string password)
    {
        /* do stuff */
    }

Это - то, как я склонен делать это, так или иначе...

2
ответ дан 3 December 2019 в 13:32
поделиться

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

На статически типизированном языке этот подход имеет некоторый смысл начиная с любых несоответствий, которые Вы представляете, будет пойман во время компиляции. Однако динамические языки будут часто только встречаться с этими проблемами во времени выполнения. Эти проблемы не были бы пойманы компилятором, а скорее Вашим набором тестов; принятие Вас записало тот.

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

1
ответ дан 3 December 2019 в 13:32
поделиться

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

1
ответ дан 3 December 2019 в 13:32
поделиться

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

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

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

Между прочим, Eclipse делает этот "сбой, тупик, запишите" метод, нелепо легкий в Java: каждый несуществующий объект отмечен для Вас, и Ctrl-1 плюс пункт меню говорит Eclipse писать (компилируемый) тупик для Вас! Мне было бы интересно знать, оказывают ли другие языки и IDE подобную поддержку.

1
ответ дан 3 December 2019 в 13:32
поделиться

Это "безопасно" в том смысле, что на достаточно проверенном во времени компиляции языке, это вынуждает Вас обновить все живые ссылки на вещь, которую Вы изменили.

Это может все еще пойти не так, как надо, если у Вас есть условно-скомпилированный-код, например, при использовании препроцессора C/C++. Поэтому удостоверьтесь, что Вы восстанавливаете во всех возможных конфигурациях и на всех платформах, если применимо.

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

void foo(int a, int b);

кому:

void foo(int a);

Затем измените вызов от:

foo(1,2);

кому:

foo(2);

Это компилирует прекрасный, но это неправильно.

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

1
ответ дан 3 December 2019 в 13:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: