Злоупотребление ссылкой: стоит ли очистить?
Я унаследовал некоторый код, который использует ключевое слово ref экстенсивно и без необходимости. Исходный разработчик, по-видимому, боялся, что объекты будут клонированы как примитивные типы, если ref не был использован и не стал исследовать проблему до написания 50k + строк кода.
Это, в сочетании с другими методами плохого кодирования, создало некоторые ситуации, которые абсурдно опасны на поверхности. Например:
Customer person = NextInLine();
//person is Alice
person.DataBackend.ChangeAddress(ref person, newAddress);
//person could now be Bob, Eve, or null
Не могли бы вы представить, как ходить в магазин, чтобы изменить свой адрес, и выйти как совершенно другой человек?
Страшно, но на практике использование ref в этом приложении кажется безвредным излишним. У меня возникли проблемы с обоснованием того, сколько времени потребуется, чтобы очистить его. Чтобы помочь продать идею, я задаю следующий вопрос:
Как иначе ненужное использование ref может быть разрушительным?
Я особенно забочусь об обслуживании. Предпочтительные варианты ответов предпочтительнее.
Вы также можете утверждать, что очистка не требуется.
Ответы
Ответ 1
Я бы сказал, что самая большая опасность состоит в том, что по какой-либо причине параметр был установлен на null
внутри функции:
public void MakeNull(ref Customer person)
{
// random code
person = null;
return;
}
Теперь вы не просто другой человек, вы полностью стерты от существования!
Пока кто-либо, разрабатывающий это приложение, понимает, что:
По умолчанию ссылки на объекты передаются по значению.
и
С ключевым словом ref
ссылки на объекты передаются по ссылке.
Если код работает так, как ожидалось сейчас, и ваши разработчики понимают разницу, вероятно, не стоит усилий, которые нужно предпринять, чтобы удалить их все.
Ответ 2
Я просто добавлю худшее использование ключевого слова ref, которое я когда-либо видел, метод выглядел примерно так:
public bool DoAction(ref Exception exception) {...}
Yup, вам нужно было объявить и передать ссылку Исключения, чтобы вызвать метод, а затем проверить значение возвращаемого метода, чтобы узнать, было ли исключение поймано и возвращено с помощью исключения ref.
Ответ 3
Можете ли вы понять, почему создатель кода подумал, что нужно, чтобы параметр был как ref? Это потому, что они обновили его, а затем удалили функциональность или просто потому, что в то время они не понимали С#?
Если вы считаете, что очистка стоит того, то продолжайте это, особенно если у вас есть время. Возможно, вы не сможете исправлять его правильно, когда возникает реальная проблема, поскольку это скорее всего будет срочным исправлением ошибок, и у вас не будет времени на выполнение надлежащей работы.
Ответ 4
В С# довольно часто изменять значения аргументов в методах, поскольку они обычно имеют значение, а не ref. Это касается как ссылочных, так и типов значений; установка ссылки на нуль, например, изменила бы исходную ссылку. Это может привести к очень странным и болезненным ошибкам, когда другие разработчики работают "как обычно". Создание рекурсивных методов с аргументами ref - это не-go.
Кроме того, у вас есть ограничения на то, что вы можете передать по ссылке. Например, вы не можете передавать постоянные значения, поля только для чтения, свойства и т.д., Так что при вызове методов с аргументами ref требуется много вспомогательных переменных.
И последнее, но не менее важное: производительность, если, вероятно, не так уж и хорошо, так как для этого требуется больше указаний (ref - это просто ссылка, которая должна быть разрешена при каждом доступе), а также может сохранять объекты дольше, поскольку ссылки не являются быстро выходя из сферы действия.
Ответ 5
Мне нравится, как разработчик С++, делающий необоснованные предположения.
Я бы с осторожностью делал оптовые изменения в чем-то, что работает. (Я предполагаю, что это работает, потому что вы не комментируете его нарушение, просто из-за того, что это опасно).
Последнее, что вы хотите сделать, это сломать что-то тонкое и потратить неделю на поиски проблемы.
Я предлагаю вам очистить, когда вы идете - по одному методу за раз.
- Найдите метод, который использует ref там, где вы уверены, что он не требуется.
- Измените подпись метода и исправьте вызовы.
- Test.
- Повтор.
В то время как конкретные проблемы, которые у вас есть, могут быть более серьезными, чем в большинстве случаев, ваша ситуация довольно распространена - наличие большой базы кода, которая не соответствует нашему нынешнему пониманию "правильного пути", чтобы делать что-то.
Оптовые "обновления" часто сталкиваются с трудностями. Рефакторинг, как вы идете - доведение до спецификации, как вы работаете на них - гораздо безопаснее.
Здесь есть прецедент в строительной отрасли. Например, электропроводка в старых зданиях (например, 19-го века) не нуждается в трогании, если нет проблем. Однако, когда есть проблема, новая работа должна быть завершена по современным стандартам.
Ответ 6
Я попытаюсь исправить это. Просто выполните замену строки с регулярным выражением и проверьте после этого блок-тесты. Я знаю, что это может сломать код. Но как часто вы используете ref? Почти никогда, не так ли? И учитывая, что разработчик не знал, как это работает, я считаю, что он используется где-то (как он должен) еще меньше. И если код сломается - ну, откат...
Ответ 7
Как еще может ненужное использование ref быть разрушительным?
Другие ответы касаются семантических вопросов, которые, безусловно, являются самыми важными. Хороший код самодокументирован, и когда я даю параметр ref, я предполагаю, что он изменится. Если это не так, API не был самодокументирован.
Но для удовольствия, как насчет того, как мы смотрим на другой аспект - производительность?
void ChangeAddress(ref Customer person, Address address)
{
person.Address = address;
}
Здесь person
- ссылка на ссылку, поэтому при каждом доступе к ней будет отображаться какая-то косвенность. Давайте посмотрим на некоторые сборки, которые могут быть переведены на:
mov eax, [person] ; load the reference to person.
mov [eax+Address], address ; assign address to person.Address.
Теперь не ref ref:
void ChangeAddress(Customer person, Address address)
{
person.Address = address;
}
Здесь нет косвенности, поэтому мы можем избавиться от прочитанного:
mov [person+Address], address ; assign address to person.Address.
На практике можно надеяться, что .NET кэширует [person]
в версии ref
, амортизируя стоимость косвенности за несколько обращений. Вероятно, на самом деле это не будет 50% -ным сокращением числа инструкций за пределами тривиальных методов, таких как здесь.