Kod Refaktoring Teknikleri: Gerçekten İşe Yarayan Yöntemler

Summary

Kod refaktoring, yeni özellik eklemeden veya var olan özellikleri bozmadan mevcut kodu yeniden yapılandırmanın sanatıdır. Extract Method, Composing Methods, Branch-by-Abstraction ve basit koşul bloklarına bölerek karmaşık şartları çözmek gibi teknikleri öğrenin. Her tekniğin ne zaman gerekli olduğunu, ne zaman fazla olduğunu ve eski sistem üzerinde altı ay çalışmaktan çıkardığım dersleri keşfedin.

Koyu ev ofisinde iki ekran gösteren geliştirici, kod refaktoring süreci

Kod Refaktoring Teknikleri: Gerçekten İşe Yarayan Yöntemler

Çoğu geliştirici refaktoringün önemli olduğunu bilir. Ama hangisi başında kullanılır, ne kadarına kadar gidilir, ve bir codebase gerçekten bir temizlik turuna ihtiyaç duyduğu sinyalinin ne olduğu hiç net değildir. Eski bir öneri sisteminde altı ay çalışmaktan öğrendiklerim ve bir sonraki sefer farklı ne yapardığım işte burada.

Koyu ev ofisinde iki ekranda kod refaktoring sürecini gösteren geliştirici

Refaktoringün gerçekten gerekli olduğunu gösteren sinyaller

Her düzensiz görünüşlü fonksiyon dokunmaya değmez. Ciddiye almaya değer üç sinyal var:

Çoklama birçok yerde ortaya çıkıyor. Aynı veri dönüştürmeyi üç ayrı controller'da yazıyorsanız, bu stil meselesi değil. Bu bakım bombası. Bir upstream değişikliği üç yerde güncelleme anlamına gelir ve biri hakkında unutacaksınız.

Tek bir değişiklik ilgisiz özellikleri kırıyor. Ödeme akışında bir hatayı düzeltmek, kullanıcı profillerinin nasıl render edildiğini etkiler. Bağlantı çok sıkı. Codebase ayrı problemleri bir olarak ele alıyor.

Yeni mühendisler 30 dakika içinde kendilerini oryante edemiyorlar. Bu sinyal olarak eksik görülüyor. Bir dosyanın ne yaptığını tamamını okumadan anlatamıyorsanız, muhtemelen çok şey yapıyor. Appier'de boarding walkthroughs zamanlamaya başladık. Açıklaması 15 dakikadan fazla süren dosyalar refaktoring backloguna eklendi.

Kod değişiklik asimetrisi. git log --stat çalıştırın ve hangi dosyalara en sık dokunulduğunu görün. Aynı 3 dosya commit'lerinizin %60'ını oluşturursa, bu yoğunlaşma bir sinyaldir. Yüksek değişim artı yüksek karmaşıklık ilk hedef almaya değer 80/20 noktasıdır.

Refaktoringü atlayın eğer: kod çalışıyor, kimse dokunmuyor ve uzatmak için acil bir neden yok. Boy Scout Kuralı (kodu bulduğunuzdan daha temiz bırakın) iyi bir ilke ama kullanmadığınız bir alanı temizlemeyin.

Extract Method: zamanın %80'ini kullanacağınız bir teknik

Extract Method, uzun bir fonksiyonun anlamlı bir kod bloğunu çıkartıp ona bir ad vermektir. Bu ad dokümantasyondur.

Öncesi:

def process_order(order):
    # doğrula
    if not order.get('user_id'):
        raise ValueError('Kullanıcı ID eksik')
    if order.get('amount', 0) <= 0:
        raise ValueError('Geçersiz tutar')
    # indirim uygula
    if order.get('coupon') == 'LAUNCH20':
        order['amount'] = order['amount'] * 0.8
    # kaydet
    db.save(order)

Sonrası:

def process_order(order):
    validate_order(order)
    apply_discount(order)
    db.save(order)

def validate_order(order):
    if not order.get('user_id'):
        raise ValueError('Kullanıcı ID eksik')
    if order.get('amount', 0) <= 0:
        raise ValueError('Geçersiz tutar')

def apply_discount(order):
    if order.get('coupon') == 'LAUNCH20':
        order['amount'] = order['amount'] * 0.8

"Sonra" versiyonu daha uzun. Sorun değil. Her fonksiyonun şimdi var olmasının bir nedeni var ve indirim mantığı değiştiğinde (ve değişecektir), tam nereye gideceğinizi bilirsiniz.

Çaba değer eğer: orijinal fonksiyon kabaca 20-25 satırdan fazla veya bir bloğu açıklamak için "# doğrula" gibi bir yorum yazmak zorunda kalırsanız. Bu yorum çıkartılmayı bekleyen bir fonksiyon adıdır. Bir kod bloğunun üzerine # indirim uygula yazmak, metodu zaten adlandırmışsınız demektir, henüz çıkartmamışsınız.

Pratik kural: sonraki beş satırın ne yaptığını açıklayan bir satır içi yorum yazma arzusu hissederseniz, bu çıkartılmaya aday.

Composing Methods: Extract Method yeterli olmadığında

Composing daha ileri gider. Bir metodu çıkartmak yerine, bütün bir sınıfı veya modülü daha küçük, odaklanmış bileşenlere ayrıştırırsınız. Bu, bir dosya 600 satıra büyümüş ve beş ilgisiz amaç hizmet ettiğinde ihtiyacınız olan şeydir.

Desen: ayrı sorumlulukları belirleyin, her birine kendi sınıfı veya modülü verin, daha yüksek düzeyde birbirine bağlayın. Martin Fowler bunu şişmanlaşmış bir sınıfı işbirliği yapan sınıflar kümesine dönüştürmek olarak tanımlar.

Pratikte, zor kısım mekanik değil. Bir sorumluluk nerede bitip diğeri başladığını bulmak. Yararlı bir buluşsal yöntem: bir fonksiyon veya sınıfın ne yaptığını "ve" kelimesi kullanmadan açıklayabiliyorsanız, muhtemelen bir sorumluluğu vardır. "Ve" gerekirse, böleyin.

Dikişleri bulmak için başka bir yol: dosyanın başındaki importlara bakın. Tek bir modül altı ilgisiz alanı importse (veritabanı katmanı, e-posta servisi, logging, ödeme istemcisi, raporlama), çok şey yapıyor. Her ithalatç kümesi kendi modülü için adaydır.

Composing methods testlenebilirliği de geliştirir. 400 satırlık bir sınıf unit testi yapmak zordur. Net arayüzleri olan dört 100-satır sınıf mock'lanması ve izole edilmesi çok daha kolay.

Kod mimarı refaktoring diyagramı planlayan beyaz tahtada geliştirici

Branch-by-Abstraction: üretim ortamında kalarak refaktoring yapma

Bu, daha mimariye yönelik ama büyük ölçekli refaktoringı üretim ortamlarında hayatta tutmayı mümkün kılan tekniktir.

Fikir: bir uygulamadan diğerine büyük bir geçiş yapmak yerine, değiştirmek istediğiniz kod etrafına bir abstraksiyon katmanı (genellikle bir interface veya protokol) tanıtırsınız. Eski uygulama ve yeni uygulama her ikisi de arayüzü karşılar. Yeni uygulamayı bir özellik bayrağının arkasına koyabilir, her ikisini paralel çalıştırabilir ve eski olanı yeni kararlı olduktan sonra kullanımdan kaldırabilirsiniz.

Side projeler için neden önemlidir: ödeme entegrasyonunu yeniden derlemek veya ORM geçişi yapıyorsanız, branch-by-abstraction, yeni olanı hazırlanırken mevcut versiyonu üretimde çalıştırmak anlamına gelir. Üç hafta main'den sapan "refaktoring şubesi" yok.

Maliyet: temiz bir arayüz tanımlamak için daha fazla ön çalışma. Değiştirilecek şey riskli olduğunda (auth, faturalandırma, veri erişimi) ve tam bir kesinti penceresine çekemediğinizde değerlidir.

Beton bir örnek: PostgreSQL tablosuna doğrudan yazan eski bir event-tracking modülümüz vardı. Kafka tabanlı bir pipeline'a geçiş 40+ call sitesine dokunmak anlamına geliyordu. Branch-by-abstraction ile EventTracker arayüzü tanıttık, uygulamayı bir bayrak arkasında değiştirdik, iki hafta boyunca her iki hedefi paralel çalıştırdık, sonra eskisini sildik. Sıfır kesinti, sıfır acil deploy.

Koşulları Basitleştirme: çoğu insanın atladığı temizlik

Koşul mantığı karmaşıklığın gizlendiği yerdir. Beş iç içe if ifadesine sahip bir fonksiyon, beş ayrı koşuldan daha zor okunur, test edilir ve uzatılır ve guard cümleleri veya çok biçimlilik ile ifade edilir.

İç içe koşulları koruma cümleleri ile değiştir:

Öncesi:

function getDiscount(user) {
  if (user) {
    if (user.isActive) {
      if (user.plan === 'pro') {
        return 0.20;
      } else {
        return 0.05;
      }
    } else {
      return 0;
    }
  } else {
    return 0;
  }
}

Sonrası:

function getDiscount(user) {
  if (!user) return 0;
  if (!user.isActive) return 0;
  if (user.plan === 'pro') return 0.20;
  return 0.05;
}

Aynı mantık. Çok daha kolay taramak. Erken dönüş satırlarını indentasyon piramidini ortadan kaldırır ve her koşulu izole olarak okumanızı sağlar.

Koşulları çok biçimliliğe değiştir aynı if type === X kontrolü birden fazla fonksiyonda tekrarlandığında. Bir temel sınıf veya protokol tanımlayın, her tür için bir uygulama ve gönderişin call sitede olmasına izin verin. Atla eğer: sadece iki türün varsa ve üçüncü ekleme olası değilse. Abstraksiyon genel gideri, hiç büyümeyecek ikili bir seçim için değerli değildir.

Yinelenen koşulları birleştir. Aynı koşul üç ayrı yerde biraz farklı şeyler yapmadan önce kontrol ediliyorsa, koşulu adlandırılmış bir predicate fonksiyonuna çıkartın. if (isEligibleForDiscount(user)) her sefer üç kısım kontrolünü tekrarlamaktan daha iyi okunur.

Paylaşılan masada pair programming ve kod incelemesi yapan iki geliştirici

Nesneler arasında özellikleri taşıma: az kullanılan teknik

Bazen bir metod yanlış sınıfta. Kod yanlış değil ama başka bir sınıftan verilerle çalışıyor kendinden daha fazla. Bu onu oraya taşımak işareti.

Move Method deseni basittir: metodu belirleyin, gerçekten hangi sınıfa bağlı olduğunu kontrol edin, oraya taşıyın, tüm çağrıcıları güncelleyin. IDE'nizden lint kırılan referansları yakalar.

Bu en çok side projeler'de ortaya çıkar: her yere açıkça ait olmadığı fikri toplayan utility sınıfları. helpers.js veya utils.py 400 satıra büyüyen dosyalar. Belli bir noktada, bu fonksiyonlar üzerinde çalıştıkları domain nesnelerine aittir. Bir User nesnesi alıp ona beş şey yapan bir fonksiyon utils'ta değil User'da aittir.

Atla eğer erken aşamasındaysan: proje iki haftalık ve hala şeklini buluyorsa, yapıyı aşırı mühendislik yapmayın. Gerçek kullanımdan tasarım ortaya çıkmasına izin verin temiz domain modeline yatırım yapmadan. İşleri çok erken taşımak, gereksinimler değiştiğinde tekrar taşımak anlamına gelir.

Refaktoring yapmadan önce yapılması gerekenler: test temeli

Testler olmadan refaktoring yapmak sadece kumarbazlık. Temizlik yapıyorsunuz sandığınız, kırıyor olabilirsiniz.

Asgari güvenlik ağı: karakterizasyon testleri. Değiştirmek üzere olduğunuz kodun mevcut davranışını yakalayan testler yazın. Ne yapması gerektiği değil, ne gerçekten yaptığı. Bu testler tasarımı doğrulamak değil, regresyonları yakalamak için var.

Temel aldıktan sonra, küçük commit'lerde refaktoring. Commit başına bir desen. Bir şey kırılırsa, tam hangi değişiklik yaptığını bilirsiniz. Bunu alternatifle kıyaslayın: üç günlük refaktoring oturumu bir dev diff'inde commit. CI saat 2'de başarısız olduğunda, 40 değişiklikten hangisinin kırdığını bilmiyorsunuz.

Her refaktoring öncesinde pratik kontrol listesi:

Faydalı kısıtlama: commit mesajında refaktoringü tek cümleyle açıklayamıyorsanız, muhtemelen çok fazlasını yapıyorsunuz.

Verileri organize etme: her şeyi iyileştiren desen

Birçok karışık koşul mantığı veri modeli yanlış olduğu için var. Ham primitivler etrafında geçiş yaparken, her yerde tür-kontrol koşulları sonuçlanır.

Un primitive'i bir nesneyle değiştirme (string email EmailAddress olur, int cents Money olur) doğrulama mantığı için ev verir. if not re.match(EMAIL_REGEX, email) 12 yerde kontrol etmek yerine, EmailAddress yapıcısı bunu bir kez yapar.

Bu Fowler'ın Refactoring kitabındaki "Type Code'u Sınıfla Değiştir" desenidir. Göz kamaştırıcı değil ama koşul yayılımını kitaptaki hemen hemen her şeyden daha iyi azaltır.

Aynı ilke magic sayılar'a uygulanır: if status == 3 okunamaz. if status == OrderStatus.SHIPPED öyledir. Magic sayıları adlandırılmış sabitlerle değiştirme beş dakika alır ve kodu okuyan her sefer öder.

Ne zaman refaktoring yapmayacağınız: insanların atladığı vakalar

Çoğu rehber burada durur. Pratikte ne takıldığı: her codebase refaktoring yapmaya değmez ve temizliği her zaman iyi olarak görmek sadece farklı türden teknik borçtur.

Zor bir deadline öncesinde refaktoring yapmayın. Refaktoring değişiklik getir. Değişiklik risk getir. Client demosu 48 saate kaldıysa, bu zaman değil.

Silmek üzere olduğunuz kodu refaktoring yapmayın. Bir özellik sonraki sprint'te kesileceğine göre, uygulanmasını temizlemek çöp çalışma. Temizliğe yatırımdan önce silmeyi onaylayın.

Sadece stil için refaktoring yapmayın. Kod çalışıyorsa, test edildiyse ve kimse dokunmuyorsa, farklı yazmış olacağınız gerçeği değiştirmek gerekçesi değil. Regresyon riski gerçek; fayda estetik.

Açık hedef olmadan refaktoring yapmayın. "Bunu temizle" bir görev değil. "OrderProcessor'dan doğrulama mantığını özel bir Validator sınıfına çıkart" bir görevdir. Muğlak kapsam scope creep'e yol açar, bu üç haftalık bir şuba ve conflict'li merge'ü yol açar.

Mükemmel kural seti değil. Uygulanabilir olanıdır ve production codebase'te, uygulanabilir mükemmeli yener.

Altı aylık incelemeli refaktoring gerçekten neye benziyor

Her sprint'te refaktoring yapan ve gönderdiği özelliklerle hizalı bir ekip, içinde çalışması daha ucuz olan bir codebase ile sona erer. İlk yazılması daha hızlı değil ama uzatması, debug'lanması ve yeni katılımcıların katılması daha hızlı.

Solo builders için: ödül kişisel. Altı aydan sonra yeni mühendis sizsiniz. Bugün temizlediğiniz kod Ocak'ta yeniden okumazsanız anlarsınız.

Öneri sistemi projesinden altı aylık resim: 1200 satır ve test olmayan bir servis dosyası ile başladık. Her özellik sprint'inde metotları çıkarttık, her dokunulan bölüme karakterizasyon testleri ekledik ve domain'i daha iyi anlamaya başladığımız sürece şeyleri doğru sınıflara taşıdık. Altı aydan sonra, aynı mantık dokuz dosyaya ortalaması 130 satır dağıtıldı, hepsi test edildi, hepsi ne yaptığı için adlandırıldı. Deployment frekansı haftadan günlüğe çıktı. Bir hatadan kurtulma süresi kabaca %40 düştü.

Bu rakamlar bir çalışmadan değil; bir ekip, bir sistemden. Ama yön tutuyor. Daha küçük, temiz birimler değiştirmesi daha hızlı. Tüm bahis bu.

Frequently asked questions

Kod refaktoring nedir?
Kod refaktoring, dış davranış değiştirmeden var olan kodu yeniden yapılandırmanın sürecidir. Önemlidir çünkü codebase'i anlamak, uzatmak ve debug'lamak daha kolay kılar, gelecek özellik geliştirme ve hata düzeltmelerin maliyetini doğrudan azaltır.
Extract Method refaktoring tekniği nedir?
Extract Method, uzun bir fonksiyondan anlamlı bir kod bloğunu çıkartıp kendi adını verir. Ad satır içi dokümantasyon gibi davranır. Yaygın kullanılan refaktoring tekniğidir ve fonksiyon 20-25 satırı aştığında genellikle doğru ilk harekettir.
Guard Clauses nedir ve ne zaman kullanılır?
Guard Clauses, iç içe koşulları erken dönüş ifadeleriyle değiştirmeyen bir teknik. İndentasyon piramidini ortadan kaldırır ve her koşulu izole olarak okumanızı sağlar. İç içe if'ler zor okunabilir olduğunda idealdir.
Branch-by-Abstraction nedir?
Branch-by-Abstraction, yaşayan produktif sistemde büyük refaktoring yapmayı mümkün kılan bir desen. Eski ve yeni uygulamayı bir özellik bayrağının arkasında paralel çalıştırırsınız. Yeni kararlı olduğunda, eskisini kaldırırsınız. Sıfır kesinti demektir.
Refaktoring yapmadan test yazmak neden önemlidir?
Testler olmadan refaktoring kumarbazlık. Karakterizasyon testleri, mevcut davranış yakalar. Bir şey kırılırsa, tam hangi değişikliğin yaptığını bilirsiniz. Regresyon yakalamak için vardır.
Ne zaman refaktoring yapmamamalı?
Zor deadline öncesinde, silmek üzere kod, sadece stil için, açık hedef olmadan refaktoring yapmayın. Muğlak kapsam scope creep'e yol açar. Mükemmel uygulanabilir olana hep yenilir.
Composing Methods nedir?
Composing Methods, Extract Method'dan ileri. Bütün sınıfı veya modülü daha küçük, odaklanmış bileşenlere ayrıştırırsınız. 600+ satır dosya beş farklı amaç hizmet ettiğinde, bu ihtiyacınız olan şeydir.