Gunakan Git secara taktis – Blog Stack Overflow

Dalam film Free Solo, pemanjat tebing Alex Honnold berlatih untuk melakukan pendakian solo gratis El Capitan, sebuah gunung di Yosemite.

(El Capitan. Foto oleh Mike Murphy, 2005.)

Ini film bagus, tapi kalau belum nonton, pendakian solo gratis adalah saat Anda memanjat permukaan batu tanpa tali, sabuk pengaman, atau peralatan keselamatan. Jika Anda kehilangan pegangan dan jatuh, Anda akan mati. El Capitan, hanya untuk menggosoknya, adalah batu vertikal setinggi 914 meter. Mendaki bebas adalah upaya yang luar biasa, tetapi Honnold menyelesaikannya dengan melakukan untuk satu langkah pada satu waktu (bagaimanapun juga, artikel ini tentang menggunakan Git).

Honnold tidak hanya memanjat bebas El Capitan. Dia berlatih dengan sengaja menuju tujuan pendakian bebas El Capitan.

Film dokumenter menunjukkan bagaimana dia berulang kali mendaki El Capitan dengan peralatan keselamatan. Dia merencanakan rute dan mendakinya beberapa kali. Pada setiap kenaikan pelatihan, dia menggunakan tali, harness, dan berbagai pengencang untuk tali. Ketika dia jatuh selama latihan, dia tidak jatuh jauh, karena tali, harness, dan pengencang menghentikan jatuhnya di titik fiksasi terakhir.

Ini hampir seperti titik penyimpanan video game.

Dalam satu adegan yang tak terlupakan, Honnold menganggap lompatan dari satu posisi ke posisi lain. Ratusan meter di udara, sejajar dengan permukaan batu vertikal. Ini adalah manuver yang benar-benar berbahaya. Jika dia gagal, dia akan mati.

Atau, itu benar untuk pendakian gratis. Pada awalnya, ia berlatih gerakan menggunakan tali dan harness. Hal ini memungkinkan dia untuk melakukan lompatan yang berpotensi fatal dengan relatif aman. Ketika terjadi kesalahan, dia kembali ke tempat dia memperbaiki talinya, dan dia mungkin mencoba lagi.

Saat Anda membuat perubahan kode yang besar, bahkan bermigrasi ke implementasi baru, Anda dapat membuat titik penyimpanan untuk mencegah bencana. Seperti Alex Honold, Anda dapat memperbaiki kode Anda di tempat untuk memberi Anda kesempatan yang lebih baik untuk mencapai build sukses berikutnya.

Saat Anda mengedit kode, Anda berpindah dari satu status kerja ke status lainnya, tetapi selama proses tersebut, kode tidak selalu berjalan atau dikompilasi.

Pertimbangkan antarmuka seperti ini:

public interface IReservationsRepository
 {
     Task Create(Reservation reservation);
  
     Task<IReadOnlyCollection<Reservation>> ReadReservations(
         DateTime dateTime);
  
     Task<Reservation?> ReadReservation(Guid id);
  
     Task Update(Reservation reservation);
  
     Task Delete(Guid id);
 }

Ini, karena sebagian besar kode dalam artikel ini, berasal dari buku saya Kode yang Sesuai di Kepala Anda. Seperti yang saya jelaskan di bagian pola Strangler Fig, pada satu titik saya harus menambahkan metode baru ke antarmuka. Metode baru harus menjadi kelebihan dari ReadReservations metode dengan tanda tangan ini:

Task<IReadOnlyCollection<Reservation>> ReadReservations(DateTime min, DateTime max);

Namun, begitu Anda mulai mengetik definisi metode itu, kode Anda tidak lagi berfungsi:

Task<IReadOnlyCollection<Reservation>> ReadReservations(     DateTime dateTime);   T   Task<Reservation?> ReadReservation(Guid id);

Jika Anda mengedit di Visual Studio, itu akan segera menyala dengan garis bawah berlekuk-lekuk merah, yang menunjukkan bahwa kode tidak diurai.

Anda harus mengetikkan seluruh deklarasi metode sebelum garis merah berlekuk-lekuk menghilang, tetapi meskipun demikian, kode tidak dapat dikompilasi. Sementara definisi antarmuka mungkin valid secara sintaksis, menambahkan metode baru merusak beberapa kode lain. Basis kode berisi kelas yang mengimplementasikan IReservationsRepository antarmuka, tetapi tidak ada yang mendefinisikan metode yang baru saja Anda tambahkan. Kompiler mengetahui hal ini, dan mengeluh:

Kesalahan CS0535 ‘SqlReservationsRepository’ tidak mengimplementasikan anggota antarmuka ‘IReservationsRepository.ReadReservations(DateTime, DateTime)’

Tidak ada yang salah dengan itu. Saya hanya mencoba menyoroti bagaimana kode pengeditan melibatkan transisi antara dua status kerja:

Di Solo Gratis seluruh pendakian itu berbahaya, tetapi ada manuver yang sangat berbahaya yang harus dilakukan Alex Honnold karena dia tidak dapat menemukan rute yang lebih aman. Untuk sebagian besar pendakian, dia memanjat menggunakan teknik yang lebih aman, bergerak dari satu posisi ke posisi lain sedikit demi sedikit, tidak pernah kehilangan pegangan atau pijakan saat dia menggeser pusat gravitasinya.

Ada alasan mengapa dia lebih suka memanjat seperti itu. Ini lebih aman.

Anda tidak dapat mengedit kode tanpa merusaknya untuk sementara. Apa yang dapat Anda lakukan, bagaimanapun, adalah bergerak dalam langkah-langkah kecil yang disengaja. Setiap kali Anda mencapai titik di mana kode dikompilasi dan semua tes lulus: komit perubahan ke Git.

Tim Ottinger menyebut ini sebagai komitmen mikro. Anda tidak hanya harus melakukan setiap kali Anda memiliki bilah hijau — Anda harus dengan sengaja memindahkan sedemikian rupa sehingga jarak antara dua komitmen sesingkat mungkin. Jika Anda dapat memikirkan cara alternatif untuk mengubah kode, pilih jalur yang menjanjikan langkah terkecil.

Mengapa membuat lompatan berbahaya ketika Anda bisa maju dalam gerakan kecil yang terkendali?

Git adalah alat yang luar biasa untuk kemampuan manuver. Kebanyakan orang tidak berpikir seperti itu. Mereka mulai memprogram, dan beberapa jam kemudian, mereka mungkin berkomitmen ke Git untuk mendorong cabang keluar.

Tim Ottinger tidak melakukan itu, dan saya juga tidak. Saya menggunakan Git secara taktis.

Saya akan memandu Anda melalui sebuah contoh.

Seperti yang dijelaskan di atas, saya ingin menambahkan ReadReservations kelebihan beban IReservationsRepository antarmuka. Motivasi untuk itu dijelaskan dalam Kode yang Sesuai di Kepala Anda, tetapi bukan itu intinya di sini. Intinya adalah menggunakan Git untuk bergerak sedikit demi sedikit.

Saat Anda menambahkan metode baru ke antarmuka yang sudah ada, basis kode gagal dikompilasi saat Anda memiliki kelas yang mengimplementasikan antarmuka tersebut. Bagaimana Anda menghadapi situasi itu? Apakah Anda terus maju dan menerapkan metode baru? Atau ada alternatif?

Inilah jalur alternatif yang bergerak dalam peningkatan yang lebih kecil.

Pertama, bersandar pada kompiler (seperti yang dikatakan Bekerja Secara Efektif dengan Kode Warisan). Kesalahan kompiler memberi tahu Anda kelas mana yang tidak memiliki metode baru. Dalam basis kode contoh, itu SqlReservationsRepository dan FakeDatabase. Buka salah satu file kode itu, tetapi jangan lakukan apa pun. Sebagai gantinya, salin yang baru ReadReservations deklarasi metode ke clipboard. Kemudian simpan perubahan:

$ git stash

Saved working directory and index state WIP on tactical-git: [...]

Kode sekarang kembali dalam keadaan bekerja. Sekarang temukan tempat yang bagus untuk menambahkan metode baru ke salah satu kelas yang mengimplementasikan antarmuka.

Saya akan mulai dengan SqlReservationsRepository kelas. Setelah saya menavigasi ke baris di file tempat saya ingin menambahkan metode baru, saya menempelkan deklarasi metode:

Task<IReadOnlyCollection<Reservation>> ReadReservations(DateTime min, DateTime max);

Itu tidak dikompilasi karena metode diakhiri dengan titik koma dan tidak memiliki tubuh.

Jadi saya membuat metode publichapus titik koma, dan tambahkan tanda kurung kurawal:

public Task<IReadOnlyCollection<Reservation>> ReadReservations(DateTime min, DateTime max) {  } 

Ini masih tidak dapat dikompilasi, karena deklarasi metode berjanji untuk mengembalikan nilai, tetapi isinya kosong.

Apa cara terpendek untuk sistem kerja?

public Task<IReadOnlyCollection<Reservation>> ReadReservations(DateTime min, DateTime max)
 {
     throw new NotImplementedException();
 }

Anda mungkin tidak ingin melakukan kode yang melempar NotImplementedExceptiontapi ini adalah metode baru yang tidak memiliki penelepon. Kode dikompilasi dan semua tes lulus—tentu saja: tidak ada kode yang diubah.

Lakukan perubahan:

$ git add . && git commit [tactical-git 085e3ea] Add ReadReservations overload to SQL repo  1 file changed, 5 insertions(+)

Ini adalah sebuah simpan poin. Menyimpan kemajuan Anda memungkinkan Anda untuk mundur dari pekerjaan ini jika sesuatu yang lain muncul. Anda tidak perlu mendorong komit itu di mana pun. Jika Anda merasa jijik tentang itu NotImplementedExceptionmerasa nyaman bahwa itu ada secara eksklusif di hard drive Anda.

Perpindahan dari kondisi kerja lama ke kondisi kerja baru membutuhkan waktu kurang dari satu menit.

Langkah alami berikutnya adalah menerapkan metode baru. Anda dapat mempertimbangkan untuk melakukan ini secara bertahap juga, menggunakan TDD saat Anda pergi, dan melakukan setelah masing-masing hijau dan faktor ulang langkah (dengan asumsi Anda mengikuti daftar periksa refactor merah-hijau).

Saya tidak akan melakukannya di sini karena saya mencoba untuk tetap SqlReservationsRepository sebuah Objek Rendah Hati. Implementasinya akan berubah menjadi kompleksitas siklomatik 2. Ditimbang dengan seberapa banyak kesulitan untuk menulis dan memelihara tes integrasi database, saya menganggap itu cukup rendah untuk melupakan menambahkan tes (tetapi jika Anda tidak setuju, tidak ada yang mencegah Anda menambahkan tes dalam langkah ini).

public async Task<IReadOnlyCollection<Reservation>> ReadReservations(DateTime min, DateTime max)
 {
     const string readByRangeSql = @"
         SELECT [PublicId], [Date], [Name], [Email], [Quantity]
         FROM [dbo].[Reservations]
         WHERE @Min <= [Date] AND [Date] <= @Max";
  
     var result = new List<Reservation>();
  
     using var conn = new SqlConnection(ConnectionString);
     using var cmd = new SqlCommand(readByRangeSql, conn);
     cmd.Parameters.AddWithValue("@Min", min);
     cmd.Parameters.AddWithValue("@Max", max);
  
     await conn.OpenAsync().ConfigureAwait(false);
     using var rdr = await cmd.ExecuteReaderAsync().ConfigureAwait(false);
     while (await rdr.ReadAsync().ConfigureAwait(false))
         result.Add(
             new Reservation(
                 (Guid)rdr["PublicId"],
                 (DateTime)rdr["Date"],
                 new Email((string)rdr["Email"]),
                 new Name((string)rdr["Name"]),
                 (int)rdr["Quantity"]));
  
     return result.AsReadOnly();
 }

Memang, ini membutuhkan waktu lebih dari satu menit untuk menulis, tetapi jika Anda pernah melakukan hal semacam ini sebelumnya, mungkin dibutuhkan kurang dari sepuluh—terutama jika Anda sudah mengetahui SELECT pernyataan sebelumnya, mungkin dengan bereksperimen dengan editor kueri.

Sekali lagi, kode dikompilasi dan semua tes lulus. Melakukan:

$ git add . && git commit
 [tactical-git 6f1e07e] Implement ReadReservations overload in SQL repo
  1 file changed, 25 insertions(+), 2 deletions(-)

Status sejauh ini: Kami melakukan dua komit, dan semua kode berfungsi. Waktu yang dihabiskan untuk pengkodean di antara setiap komit sangat singkat.

Kelas lain yang mengimplementasikan IReservationsRepository disebut FakeDatabase. Ini adalah Objek Palsu (semacam Uji Ganda) yang ada hanya untuk mendukung pengujian otomatis.

Proses untuk menerapkan metode baru ini persis sama dengan untuk SqlReservationsRepository. Pertama, tambahkan metode:

public Task<IReadOnlyCollection<Reservation>> ReadReservations(DateTime min, DateTime max)
 {
     throw new NotImplementedException();
 }

Kode dikompilasi dan semua tes lulus. Melakukan:

$ git add . && git commit
 [tactical-git c5d3fba] Add ReadReservations overload to FakeDatabase
  1 file changed, 5 insertions(+)

Kemudian tambahkan implementasinya:

public Task<IReadOnlyCollection<Reservation>> ReadReservations(DateTime min, DateTime max)
 {
     return Task.FromResult<IReadOnlyCollection<Reservation>>(
         this.Where(r => min <= r.At && r.At <= max).ToList());
 }

Kode dikompilasi dan semua tes lulus. Melakukan:

$ git add . && git commit
 [tactical-git e258575] Implement FakeDatabase.ReadReservations overload
  1 file changed, 2 insertions(+), 1 deletion(-)

Masing-masing komit ini hanya mewakili beberapa menit waktu pemrograman; itulah intinya. Dengan sering melakukan, Anda memiliki poin penyimpanan granular yang dapat Anda mundur jika ada yang salah.

Ingatlah bahwa kami telah menambahkan metode untuk mengantisipasi bahwa IReservationsRepository antarmuka akan berubah. Itu belum berubah, ingat. Saya menyembunyikan hasil edit itu.

Metode baru sekarang ada di mana pun yang diperlukan: keduanya aktif SqlReservationsRepository dan FakeDatabase.

Sekarang pop simpanan:

$ git stash pop On branch tactical-git
 Changes not staged for commit:
   (use "git add <file>..." to update what will be committed)
   (use "git restore <file>..." to discard changes in working directory)
         modified:   Restaurant.RestApi/IReservationsRepository.cs
 
 no changes added to commit (use "git add" and/or "git commit -a")
 Dropped refs/stash@{0} (4703ba9e2bca72aeafa11f859577b478ff406ff9)

Ini menambahkan kembali ReadReservations kelebihan metode ke antarmuka. Ketika saya pertama kali mencoba melakukan ini, kode tidak dapat dikompilasi karena kelas yang mengimplementasikan antarmuka tidak memiliki metode itu.

Sekarang, di sisi lain, kode segera dikompilasi dan semua tes lulus. Melakukan.

$ git add . && git commit
 [tactical-git de440df] Add ReadReservations overload to repo interface
  1 file changed, 2 insertions(+)

Dilakukan. Dengan aplikasi taktis dari git stashadalah mungkin untuk membagi apa yang tampak seperti satu manuver yang panjang dan tidak aman menjadi lima langkah yang lebih kecil dan lebih aman.

Seseorang pernah, secara sepintas, menyebutkan bahwa seseorang tidak boleh lebih dari lima menit dari komit. Itu ide yang sama. Saat Anda mulai mengedit kode, bantulah diri Anda sendiri untuk bergerak sedemikian rupa sehingga Anda bisa mendapatkan status kerja baru dalam lima menit.

Ini tidak berarti bahwa Anda harus berkomitmen setiap lima menit. Tidak apa-apa meluangkan waktu untuk berpikir. Kadang-kadang, saya pergi berlari, atau berbelanja bahan makanan, untuk membiarkan otak saya mencerna suatu masalah. Terkadang, saya hanya duduk dan melihat kode tanpa mengetik apa pun. Dan terkadang, saya mulai mengedit kode tanpa rencana yang baik, dan tidak apa-apa juga… Seringkali, dengan berlama-lama dengan kode, inspirasi datang kepada saya.

Ketika itu terjadi, kode mungkin dalam keadaan tidak konsisten. Mungkin itu mengkompilasi; mungkin tidak. Tidak apa-apa. Saya selalu dapat mengatur ulang ke titik penyimpanan terbaru saya. Seringkali, saya mengatur ulang dengan menyembunyikan hasil eksperimen saya yang setengah matang. Dengan begitu, saya tidak membuang apa pun yang mungkin menjadi berharga, tetapi saya masih bisa memulai dengan awal yang bersih.

git stash mungkin adalah perintah yang paling sering saya gunakan untuk meningkatkan kemampuan manuver. Setelah itu, dapat berpindah antar cabang secara lokal juga berguna. Terkadang, saya membuat prototipe cepat dan kotor di satu cabang. Begitu saya merasa bahwa saya memahami arah yang harus saya tuju, saya berkomitmen ke cabang itu, mengatur ulang pekerjaan saya ke komit yang lebih tepat, membuat cabang baru dan melakukan pekerjaan itu lagi, tetapi sekarang dengan tes atau hal lain yang saya lewati selama Prototipe.

Mampu menyimpan perubahan juga bagus ketika Anda menemukan bahwa kode yang Anda tulis sekarang membutuhkan sesuatu yang lain untuk ditempatkan (misalnya metode pembantu yang belum ada). Simpan perubahan, tambahkan hal yang baru Anda pelajari, komit, dan keluarkan simpanan. Ayat 11.1.3 Pemfaktoran Ulang Terpisah dari Kode Uji dan Produksi dalam Kode yang Sesuai di Kepala Anda berisi contoh tentang itu.

saya juga menggunakan git rebase banyak. Meskipun saya bukan penggemar pemusnahan komit, saya tidak keberatan menyusun ulang komit di cabang Git lokal saya. Selama saya belum berbagi komitmen dengan dunia, menulis ulang sejarah bisa bermanfaat.

Git memungkinkan Anda untuk bereksperimen, mencoba satu arah, dan mundur jika arah mulai terlihat seperti jalan buntu. Simpan saja atau komit perubahan Anda, kembali ke titik penyimpanan sebelumnya dan coba arah alternatif. Ingatlah bahwa Anda dapat meninggalkan sebanyak mungkin cabang yang tidak lengkap di hard drive Anda. Anda tidak perlu mendorong mereka ke mana pun.

Itu yang saya pertimbangkan penggunaan taktis Git. Ini adalah manuver yang Anda lakukan untuk menjadi produktif dalam skala kecil. Artefak dari gerakan ini tetap ada di hard drive lokal Anda, kecuali jika Anda secara eksplisit memilih untuk membaginya dengan orang lain.

Git adalah alat dengan potensi lebih dari yang disadari kebanyakan orang. Biasanya, programmer menggunakannya untuk menyinkronkan pekerjaan mereka dengan orang lain. Jadi, mereka menggunakannya hanya ketika mereka merasa perlu untuk melakukan itu. itu git push dan git pull.

Meskipun itu adalah fitur Git yang berguna dan penting, jika hanya itu yang Anda lakukan, Anda sebaiknya menggunakan sistem kontrol sumber terpusat.

Nilai Git adalah keuntungan taktis yang juga diberikannya. Anda dapat menggunakannya untuk bereksperimen, membuat kesalahan, memukul, dan berjuang di mesin lokal Anda, dan kapan saja, Anda dapat mengatur ulang jika semuanya menjadi terlalu sulit.

Dalam artikel ini, Anda melihat contoh penambahan metode antarmuka, hanya untuk menyadari bahwa ini melibatkan lebih banyak pekerjaan daripada yang mungkin Anda pikirkan sebelumnya. Alih-alih hanya mendorong manuver tidak aman yang direncanakan dengan buruk yang tidak memiliki akhir yang jelas, mundur saja dengan menyembunyikan perubahan sejauh ini. Kemudian pindahkan dengan sengaja dalam langkah-langkah yang lebih kecil dan akhirnya keluarkan simpanan.

Sama seperti pemanjat tebing seperti Alex Honnold yang berlatih dengan tali dan harness, Git memungkinkan Anda untuk melanjutkan langkah kecil dengan opsi mundur. Gunakan untuk keuntungan Anda.

Tags: git, micro-commits, pola ara pencekik