Во время обсуждения один из моих коллег сказал, что у него есть некоторые трудности с его текущим проектом, когда он пытается устранить ошибки. «Когда я решаю одну ошибку, что-то еще перестает работать в другом месте», - сказал он.
Я начал думать о том, как это могло произойти, но не могу понять это.
- Иногда у меня возникают похожие проблемы, когда я слишком устал или сонлив, чтобы правильно выполнять работу и иметь общее представление о той части кода, над которой я работал. Здесь проблема, кажется, в течение нескольких дней или недель, и не связана с фокусом моего коллеги.
- Я также могу представить себе эту проблему, возникающую в очень большом проекте с очень плохим управлением , когда товарищи по команде не имеют ни малейшего представления о том, кто что делает, и какое влияние на работу других может оказать изменение, которое они делают. Это также не тот случай: это довольно маленький проект с одним разработчиком.
- Это также может быть проблемой со старой, плохо обслуживаемой и никогда не документированной кодовой базой , где единственные разработчики, которые действительно могут вообразить последствия изменений, покинули компанию много лет назад. Здесь проект только начался, и разработчик не использует чью-либо кодовую базу.
Итак, что может быть причиной такой проблемы на новой небольшой базе кода, написанной одним разработчиком, который остается сосредоточенным на своей работе ?
Что может помочь?
- Модульные тесты (их нет)?
- Правильная архитектура (я почти уверен, что кодовая база вообще не имеет архитектуры и была написана без предварительного обдумывания), требующая полного рефакторинга?
- Парное программирование?
- Что-то другое?
project-management
refactoring
Арсений Мурзенко
источник
источник
Ответы:
Это не имеет ничего общего с фокусом, размером проекта, документацией или другими проблемами процесса. Подобные проблемы, как правило, являются результатом чрезмерного сцепления в дизайне, что очень затрудняет выделение изменений.
источник
Одной из причин может быть тесная связь между компонентами вашего программного обеспечения: если нет простых, четко определенных интерфейсов между компонентами, то даже небольшое изменение в одной части кода может привести к неожиданным побочным эффектам в других частях код.
Как пример, в последнее время я работал над классом, который реализует компонент GUI в моем приложении. В течение нескольких недель сообщалось о новых ошибках, я исправлял их, и новые ошибки появлялись где-то еще. Я понял, что этот класс стал слишком большим, делал слишком много вещей, и многие методы зависели от других методов, вызываемых в правильной последовательности для правильной работы.
Вместо того, чтобы исправлять последние три ошибки, я провел сильный рефакторинг: разделил компонент на основной класс плюс классы MVC (три дополнительных класса). Таким образом, мне пришлось разбить код на более мелкие и простые части и определить более понятные интерфейсы. После рефакторинга все ошибки были устранены, и о новых ошибках не сообщалось.
источник
Для одного жука легко замаскировать другого. Предположим, ошибка «A» приводит к неправильной функции, вызываемой для обработки ввода. Когда ошибка «A» исправлена, неожиданно вызывается правильная функция, которая никогда не проверялась.
источник
Ну, непосредственная причина состоит в том, что два неправильных делают правильное или, по крайней мере, делают не очевидное неправильное. Одна часть кода компенсирует некорректное поведение другой части. Или, если первая часть не является «неправильной» как таковой, существует неписаное соглашение между двумя частями, которое нарушается при изменении кода.
Например, предположим, что функции A и B используют нулевое соглашение для некоторого количества, поэтому они работают вместе правильно, но C использует один, вы можете «исправить» A для работы с C, а затем обнаружить проблему с B.
Более глубокая проблема заключается в отсутствии независимой проверки правильности отдельных частей. Модульные тесты предназначены для решения этой проблемы. Они также выступают в качестве спецификации правильных входных данных. Например, хороший набор тестов дал бы понять, что функции A и B ожидают ввод на основе 0, а на основе C 1.
Правильное получение спецификаций также может быть выполнено другими способами, от официальных документов до хороших комментариев в коде, в зависимости от потребностей проекта. Ключом является понимание того, что ожидает каждый компонент и что он обещает, поэтому вы можете найти несоответствия.
Хорошая архитектура помогает с проблемой понимания кода, делая это проще. Парное программирование полезно для того, чтобы избежать ошибок в первую очередь или быстрее их найти.
Надеюсь это поможет.
источник
Тесная связь, отсутствие тестирования, это, пожалуй, самые распространенные виновники. В основном общая проблема - просто некачественные стандарты и процедуры. Другой - просто неправильный код, который на какое-то время везет с правильным поведением. Рассмотрим
memcpy
ошибку Линуса Торвальдса, когда изменение его реализации выявляло (не вызывало) ошибки в клиентах, которые использовалисьmemcpy
в местах, где они должны были использоватьсяmemmove
с перекрытием источника и назначения.источник
Похоже, что эти "новые" ошибки на самом деле не являются "новыми" ошибками. Они не были проблемой, пока другой код, который был сломан, не был исправлен. Другими словами, ваш коллега не понимает, что у него все время было две ошибки. Если код, который не будет взломан, не был взломан, он не потерпел бы неудачу, как только был исправлен другой фрагмент кода.
В обоих случаях может быть полезен лучший автоматизированный режим тестирования. Похоже, ваш коллега должен провести модульное тестирование текущей базы кода. В будущем регрессионное тестирование подтвердит, что существующий код продолжает функционировать.
источник
Повысьте широту своего автоматизированного режима тестирования. ВСЕГДА запускайте полный набор тестов до внесения изменений в код. Таким образом, вы обнаружите пагубный эффект ваших изменений.
источник
Я только столкнулся с этим, когда тест был неверным. Тест проверил данное состояние разрешения, которое было! Правильным. Я обновил код и запустил тест разрешения. Это сработало. Затем я запустил все тесты. Все остальные тесты, которые использовали проверенный ресурс, не прошли. Я исправил тест и проверку разрешения, но сначала была небольшая паника.
Несоответствующие спецификации также случаются. Тогда почти гарантировано, что исправление одной ошибки приведет к созданию другой (захватывающей, когда эта конкретная часть спецификации не будет реализована до тех пор, пока в проекте не будет реализовано позднее).
источник
Представьте, что у вас есть готовый продукт. Затем вы добавляете что-то новое, все кажется нормальным, но вы сломали что-то еще, что зависит от некоторого кода, который вы изменяете, чтобы новая функция работала. Даже если вы не измените какой-либо код, просто добавьте функциональность к существующему, он может сломать что-то еще.
В общем, вы почти ответили себе:
Просто научитесь адаптировать принцип TDD (по крайней мере, для новых функций) и попробуйте проверить все возможные состояния, которые могут произойти.
Парное программирование великолепно, но не всегда «доступно» (время, деньги, оба…). Но проверки кода (например, вашими коллегами) один раз в день / неделю / набор коммитов тоже очень помогут, особенно, когда обзор включает набор тестов. (Мне трудно не писать сообщения об ошибках в тестовых пакетах ... иногда я должен тестировать тест изнутри (проверка работоспособности) :)).
источник
Допустим, разработчик А написал код с ошибкой. Код не совсем то, что он должен делать, но что-то немного другое. Разработчик B написал код, который полагался на то, что код A выполняет именно то, для чего он предназначен, а код B не работает. Б исследует, находит неправильное поведение в коде А и исправляет его.
Между тем код разработчика С работал правильно, потому что он полагался на некорректное поведение кода А. Код А теперь правильный. И код С перестает работать. Это означает, что когда вы исправляете код, вам нужно очень тщательно проверить, кто использует этот код, и как их поведение изменится с фиксированным кодом.
У меня была другая ситуация: некоторый код плохо себя вел и в какой-то ситуации X полностью прекратил работу функции. Поэтому я изменил неправильное поведение и заставил функцию работать. К сожалению, побочный эффект заключался в том, что весь объект имел значительные проблемы в ситуации X и повсеместно проваливался - это было абсолютно неизвестно никому, потому что ситуация никогда не возникала раньше. Ну, это сложно.
источник