Автор книг Dependency Injection in .NET («Внедрение зависимостей на платформе .NET») и Code That Fits in Your Head рассказывает о своём подходе к Git и git stash, позволяющем добиться большой гибкости в работе с кодом. Опытом Марка Симана делимся к старту курса по разработке на С#. В фильме Фри-соло скалолаз Алекс Хоннольд тренировался в свободном восхождении, чтобы покорить гору Эль-Капитан, в Йосемите. Это хороший фильм, но, если вы его не видели, свободное одиночное восхождение — это когда вы взбираетесь на скалу без защитного снаряжения, обвязки и верёвок. Вот Эль-Капитан, просто чтобы вы представляли его, — это 914 метров отвесной скалы: Потеряв хватку и упав, вы умрёте. Свободное восхождение — это невероятное усилие, но Хоннольд покоряет скалу, делая одно движение за один раз; в конце концов, эта статья о работе с Git. Хоннольд не просто так свободно поднимался по Эль-Капитану. Для этого он сознательно тренировался. Документальный фильм показывает, как множество раз он поднимается на Эль-Капитан в защитном снаряжении, планирует маршрут и поднимается по нему несколько раз.  На каждом восхождении он использует верёвки, обвязку и различные крепления верёвок. Хоннольд не падает далеко: верёвка, упряжь и крепёж останавливают падение в последней точке фиксации, почти как сохранение в игре. Сильно изменяя код, даже при переходе на новую реализацию, чтобы избежать катастрофы, можно создавать точки сохранения. Как и Алекс Хоннольд, вы можете исправить код, чтобы иметь больше шансов добраться до следующей успешной сборки. Редактируя код, вы переходите от одного рабочего состояния к другому, но во время редактирования код не всегда выполняется или компилируется. Рассмотрим такой интерфейс: Этот код, как и большая часть кода в статье, из моей книги Code That Fits in Your Head. Как я рассказываю в разделе о паттерне Strangler Fig, в какой-то момент мне пришлось добавить новый метод в интерфейс. Он должен был перегружаться методом ReadReservations с такой сигнатурой: Однако, как только вы начнёте вводить это определение метода, код перестанет работать: Если вы работаете в Visual Studio, редактор сразу подчеркнёт код красными волнистыми линиями, указывающими на неудачу синтаксического анализа. Чтобы все волнистые линии исчезли, необходимо ввести все объявления метода но даже тогда код не скомпилируется. Определение интерфейса может быть синтаксически корректным, но добавление нового метода сломает другой код. Кодовая база содержит классы, реализующие интерфейс IReservationsRepository, но ни один из этих классов не определяет только что добавленный метод. Компилятор знает об этом и жалуется: Error CS0535 'SqlReservationsRepository' does not implement interface member 'IReservationsRepository.ReadReservations(DateTime, DateTime)' В этом нет ничего плохого. Я просто подчёркиваю, что редактирование кода включает в себя переход между двумя рабочими состояниями: В фильме подъём опасен, но есть особенно опасный манёвр, который Алекс Хоннольд должен сделать из-за того, что не может найти безопасный маршрут.  Большую часть времени восхождения он поднимается безопасными методами, перемещаясь от положения к положению короткими движениями и никогда не теряя сцепления при смещении центра тяжести: это безопаснее. Нельзя редактировать код, не ломая его, но можно делать небольшие, осознанные шаги, фиксируя изменения в Git каждый раз, когда код компилируется, а тесты проходят успешно. Тим Оттингер [работавший в консалтинговой компании Роберта С. Мартина Object Mentor] называет это микрокоммитами. Вы не только должны фиксировать все изменения всякий раз, когда проходят тесты и компиляция, но вы сознательно должны продвигаться так, чтобы расстояние между двумя коммитами было наименьшим.  Если можно придумать альтернативные изменения кода, выберите путь с наименьшими шагами: зачем делать опасные прыжки, когда можно продвигаться небольшими управляемыми движениями? Git — удивительно манёвренный инструмент. Большинство людей не думают о нём таким образом. Они начинают программировать, а зафиксировать изменения могут лишь часы спустя, чтобы отправить ветку в удалённый репозиторий. Тим Оттингер так не делает, и я тоже. Я работаю с Git тактически и расскажу вам, как. Как я сказал выше, мне хотелось добавить перегрузку ReadReservations в интерфейс IReservationsRepository. Причина желания раскрывается в Code That Fits Your Head, но суть не в ней, а в том, чтобы использовать Git, продвигаясь малыми приращениями. Добавляя метод к существующему интерфейсу, вы нарушаете компиляцию кодовой базы, пока в ней существуют реализующие этот интерфейс классы. Как с этим разобраться? Просто продвигаться вперёд, реализуя новый метод, или есть другие подходы? Альтернатива — продвижение меньшими шагами. Полагайтесь на компилятор, как сказано в Working Effectively with Legacy Code. Ошибки компилятора укажут на классы, в которых нет нового метода; в кодовой базе примера это SqlReservationRepository и FakeDatabase.  Откройте эти файлы, скопируйте объявление метода ReadReservations в буфер обмена и скройте изменения в stash: Код снова работает. Найдите подходящее место, чтобы добавить метод в один из классов, реализующих интерфейс. Я начну с класса SqlReservationsRepository. Перейдя к строке, в которую хочется добавить новый метод, я вставляю его объявление: Код не компилируется потому, что метод заканчивается точкой с запятой и не имеет тела. Я делаю метод открытым, удаляю точку с запятой и дописываю фигурные скобки: Код по-прежнему не компилируется: объявление обещает вернуть значение, но у метода нет тела. Как же быстро прийти к работающей системе? Вы можете не захотеть фиксировать в Git код, бросающий NoImplementedException, но у нового метода нет вызывающего кода. Все тесты проходят, и код компилируются, ведь он не изменялся. Зафиксируем изменения: Это точка сохранения. Сохранение прогресса позволяет вернуться, когда появится что-то ещё. Отправлять код никуда не нужно, а если из-за NoImplementedException вы чувствуете неловкость, утешайте себя тем, что  это исключение существует только на вашем жёстком диске. Переход от старого рабочего состояния к новому занял меньше минуты. Естественно, следующий шаг — реализовать новый метод. Можно рассматривать эти приращения, по ходу процесса используя TDD и фиксируя изменения после успешных компиляции и тестирования, а также рефакторинга, предполагая следование чек-листу рефакторинга red-green. Я не буду делать это здесь, потому что пытаюсь сохранить SqlReservationsRepository как Humble Object. Реализация будет иметь цикломатическую сложность, равную 2. Учитывая сложность написания и поддержки теста интеграции с базой данных, я считаю, что это достаточно низкий уровень, чтобы отказаться от теста. Но, если вы не согласны, ничто не мешает добавить тесты на этом этапе. Конечно, это занимает больше минуты, но если вы делали подобные вещи раньше, то, вероятно, займёт меньше времени, особенно если вы предварительно получили результат SELECT, возможно, поэкспериментировав с редактором запросов. Код снова компилируется, все тесты проходят. Фиксируем изменения: У нас два коммита, и весь код работает, а кодирование между коммитами заняло не много времени. Другой класс, реализующий IReservationsRepository, называется FakeDatabase. Это заглушка, своего рода дублёры, только для поддержки автоматизированного тестирования. Новый метод реализуется точно так же, как в SqlReservationsRepository.  Сначала добавьте этот метод: Код компилируется, все тесты проходят. Фиксируем изменения: Добавим реализацию: И снова код компилируется, тесты проходят: Каждый из этих коммитов занимает всего несколько минут; в этом весь смысл. Делая коммиты часто, вы оставляете точки сохранения; если что-то пойдёт не так, вы сможете отступить к ним. Имейте в виду, что методы добавляются в ожидании изменения интерфейса IReservationsRepository, но сам интерфейс ещё не изменился: я скрыл его изменение. Теперь новый метод используется везде, где он должен быть, то есть в SqlReservationsRepository и в FakeDatabase.  Вернём скрытые изменения: Код повторно добавляет в интерфейс перегрузку метода ReadReservations. Когда я впервые попытался добавить её, код не скомпилировался: реализующие интерфейс классы не имели этого метода. Иными словами, код компилируется сразу, и все тесты проходят.  Фиксируем изменения: Вот и всё. Применяя git stash тактически, мы разделили длинный манёвр на пять более безопасных шагов. Кто-то однажды мимоходом упомянул, что никогда не следует отходить от коммита более чем на пять минут. Здесь та же идея. Начиная редактировать код, сделайте себе одолжение, двигаясь так, чтобы перейти в новое работающее состояние за пять минут. Это не значит, что коммит нужно делать каждые пять минут. Нормально, когда есть время подумать. Иногда, чтобы позволить себе обдумать проблему, я иду на пробежку или в магазин. Иногда — просто сижу и смотрю на код, или начинаю редактирование без хорошего плана, и это тоже нормально… Когда я работаю с кодом, ко мне часто приходит вдохновение. И тогда код может быть непоследовательным.  Возможно, он компилируется; или нет. Всё нормально: всегда можно вернуться к точке последнего сохранения. Часто я сбрасываю работу, скрывая результаты сырых экспериментов. Так я не выбрасываю ничего, что может стать ценным, но начинаю с чистого листа. Вероятно, командой git stash для повышения маневренности я пользуюсь чаще всего; во вторую очередь полезна возможность локального перемещения между ветками.  Иногда я делаю быстрый, грязный прототип в одной ветке, а когда чувствую, что понимаю нужное направление, то делаю коммит в эту ветку, сбрасываю работу через git reset на более подходящий коммит, создаю новую ветку и делаю работу снова, но теперь уже с тестами или чем-то другим. Возможность спрятать изменения хороша, когда вы обнаружите, что код, который вы пишете прямо сейчас, нуждается в чём-то ещё, например во вспомогательном методе, которого ещё нет.  Спрячьте изменения в stash, добавьте то, о чём вы только что узнали, зафиксируйте это и верните скрытые изменения. Пример есть в подразделе 11.1.3 Раздельный рефакторинг тестового и производственного кода книги Code That Fits in Your Head. Часто я использую git rebase. Я не сторонник объединения коммитов, но не испытываю угрызений совести по поводу изменения порядка коммитов в моих локальных ветках Git. Пока я не делюсь коммитами со всем миром, переписать историю коммитов может быть полезным. Git позволяет экспериментировать, пробовать одно направление и отказываться от него, если оно начинает казаться тупиковым. Просто сохраните или зафиксируйте свои изменения, вернитесь к предыдущей точке сохранения и попробуйте альтернативное направление.  Имейте в виду, что вы можете оставить на жёстком диске столько незаконченных веток, сколько захотите. Не нужно никуда отправлять их. Это я называю тактическим применением Git. Манёвры, выполняемые, чтобы стать продуктивнее в малом. Артефакты этих перемещений остаются на вашем локальном жёстком диске, если только вы не решите поделиться ими. Git — это инструмент с большим потенциалом, чем думают многие. Обычно программисты, чтобы синхронизировать работу с другими людьми, только когда чувствуют необходимость в git push и git pull. Хотя это полезная и важная функция Git, если это всё, что вы делаете, то можно использовать централизованную систему управления версиями. Ценность Git — в тактическом преимуществе. Можно экспериментировать, ошибаться, метаться и напрягаться на своём компьютере, и сбросить работу через git reset в любой момент, если всё станет слишком сложно. Вы видели пример добавления метода интерфейса, только чтобы понять, что это требует больше работы, чем можно подумать. Вместо того чтобы проталкивать плохо спланированный, небезопасный манёвр без чёткого завершения, просто отступите, скрыв изменения, двигайтесь небольшими шагами и, наконец, верните скрытые изменения. Так же, как скалолаз тренируется с верёвками и упряжью, Git позволяет вам двигаться небольшими шагами с запасными вариантами. Используйте это в своих интересах.  А мы поможем прокачать скиллы или с самого начала освоить профессию, востребованную в любое время: Профессия C#-разработчик Профессия Fullstack-разработчик на Python Выбрать другую востребованную профессию. Data Science и Machine Learning Профессия Data Scientist Профессия Data Analyst Курс «Математика для Data Science» Курс «Математика и Machine Learning для Data Science» Курс по Data Engineering Курс «Machine Learning и Deep Learning» Курс по Machine Learning Python, веб-разработка Профессия Fullstack-разработчик на Python Курс «Python для веб-разработки» Профессия Frontend-разработчик Профессия Веб-разработчик Мобильная разработка Профессия iOS-разработчик Профессия Android-разработчик Java и C# Профессия Java-разработчик Профессия QA-инженер на JAVA Профессия C#-разработчик Профессия Разработчик игр на Unity От основ — в глубину Курс «Алгоритмы и структуры данных» Профессия C++ разработчик Профессия Этичный хакер А также Курс по DevOps Все курсы