7 puan yazan GN⁺ 5 시간 전 | 5 yorum | WhatsApp'ta paylaş
  • Yanlış soyutlamaya kıyasla kod tekrarının çok daha ucuz olduğu ve erken ortaklaştırmanın uzun vadeli bakım maliyetini artırdığı savunuluyor
  • Başta makul görünen bir çıkarım bile gereksinimler zamanla biraz değişince parametreler ve koşullu ifadelerle dolup taşarak asıl niyeti bulanıklaştırır
  • Ortak bir soyutlama birden fazla fikri taşımaya başladığında kod, koşul odaklı bir prosedüre dönüşür ve yeni özellikler eklendikçe daha kolay kırılır hale gelir
  • Mevcut koda harcanan emeği koruma isteğiyle oluşan batık maliyet yanılgısına karşı dikkatli olunmalı; gerekirse soyutlama tekrar çağrı noktalarına inline edilerek yalnızca gerçekten gereken kod bırakılmalıdır
  • Yanlış bir soyutlama ortaya çıktıysa, tekrarları yeniden devreye alıp mevcut gereksinimlerin ortak yönlerini yeniden gözlemledikten sonra tekrar çıkarım yapmak daha hızlıdır

Yanlış soyutlamanın oluşma süreci

  • duplication is far cheaper than the wrong abstraction” sözü RailsConf 2014 sunumunun bir parçasıydı, ancak sonrasında da sıkça anılmaya devam etti
  • Yaygın başarısızlık yolu şöyledir
    • Geliştirici A bir tekrar fark eder
    • Bu tekrar bir metoda ya da sınıfa çıkarılır ve ad verilerek yeni bir soyutlama oluşturulur
    • Çağrı noktalarındaki tekrar eden kod, yeni soyutlamanın çağrılarıyla değiştirilir
    • Zaman geçtikçe neredeyse uyan ama tamamen aynı olmayan yeni bir gereksinim ortaya çıkar
    • Geliştirici B mevcut soyutlamayı korumaya çalışarak parametre ekler ve değere göre farklı yollar izleyen koşullu ifadeler yerleştirir
    • Sonrasında her yeni gereksinimle birlikte parametreler ve koşullu ifadeler artar, kodu anlamak giderek zorlaşır
  • Bir kez yazılmış kod, korunması gereken bir yatırım gibi görünmeye çok yatkındır
    • Zaten harcanmış emeği boşa gitmiş saymak istemeyen bir psikoloji devreye girer
    • Kod ne kadar karmaşık ve anlaşılması güçse, o kadar önemli ve yazması uzun sürmüş gibi algılanır; bu da ondan vazgeçmeyi zorlaştırır
    • Bu durum batık maliyet yanılgısıyla bağlantılıdır

Tekrara dönüp yeniden çıkarım yapmak

  • Yeni gereksinimler yanlış bir soyutlama üzerinde uygulanmaya devam ederse, paylaşılan kod koşullu ifadeler merkezli hale gelir ve her yeni özellikle birlikte daha da kırılganlaşır
  • Bu noktada hızlı yol daha fazla zorlamak değil, geri dönmektir
    • Soyutlanmış kod her çağrı noktasına tekrar inline edilerek tekrarlar yeniden devreye alınır
    • Her çağrı noktasında aktarılan parametrelere göre gerçekten çalışan kodun hangisi olduğu incelenir
    • O çağrı noktasında gerekmeyen kod silinir
  • Inline etme süreci hem soyutlamayı hem de koşullu ifadeleri ortadan kaldırır ve her çağrı noktasını yalnızca kendisinin ihtiyaç duyduğu koda indirger
  • Aynı soyutlamayı çağırıyor gibi görünen kodlar bile gerçekte her çağrı noktasında oldukça kendine özgü kod yolları çalıştırıyor olabilir
  • Ancak önceki soyutlama tamamen kaldırıldıktan sonra tekrarlar yeniden gözlemlenebilir ve mevcut gereksinimlere uygun yeni bir soyutlama çıkarılabilir
  • Parametreler ve koşullu yollar paylaşılan koda sürekli ekleniyorsa, o soyutlama artık uygun olmayabilir
    • Başlangıçta doğru soyutlama olmuş olabilir
    • Gereksinimler değiştikçe artık aynı biçimde sürdürülmesi zorlaşmış olabilir
  • Yanlış soyutlamada tekrarları yeniden devreye almak bir geri adım değil, daha iyi bir ilerleme olur

5 yorum

 
dieafterwork 1 시간 전

Bunun ikili bir yorum gerektiren bir konu olup olmadığından pek emin değilim.

 
hanje3765 2 시간 전

Ah, buna gerçekten katılıyorum.
Düzenlenmemiş olanı düzenleyebilirsiniz ama
zaten düzenlenmiş olanı tersine çevirmenin maliyeti daha yüksek gibi görünüyor.

 
jimmy2056 3 시간 전

ponytail bunu paylaşmıştı, tam da böyle bir yazı haha

 
shakespeares 4 시간 전

Her zaman karşı karşıya geliyor.

 
GN⁺ 5 시간 전
Hacker News görüşleri
  • Tek bir doğruluk kaynağı (single source of truth) ilkesine her zaman uyulması gerektiğini düşünüyorum
    Birbirinden saparsa bug üreten yinelenmiş kod varsa refactor edilmelidir. Aksi halde gelecekteki geliştiricilerin bug patlayana kadar fark etmesinin zor olduğu uzak mesafeli bağlaşım ortaya çıkar
    Ancak bu ilke ihlal edilmiyorsa soyutlama sadece bir kolaylıktır; rahatsız edici olmaya başladıysa görevini yapmıyordur ve kullanmak için bir neden yoktur. Bir fonksiyon özelleştirilmiş davranış için birden fazla flag gerektiriyorsa bu büyük olasılıkla hatalı bir soyutlamadır ya da tek sorumluluk ilkesinin ihlalidir
    Gerçekten çok fazla özelleştirme gerekiyorsa, argüman olarak fonksiyon/functor alan bir yaklaşım çoğu zaman iyidir. Örneğin solve(f:double -> double, max_iters = 99, x_abs_tol = 1e-15, x_rel_tol = 1e-15, ...) yerine solve(f:double -> double, stopping_criteria: StoppingCriteriaClass) gibi yapılabilir

    • Yazının özü, henüz kaç tane doğruluk kaynağı olduğu net olmayan durumları ele alıyor
      Kodun iki noktasının aynı algoritmayı mı kullandığı, yoksa biraz farklı sürümler mi olduğu ve daha da önemlisi aynı nedenle değişip değişmeyeceği belirsiz
      Başlıktaki deyiş, farklı şeyleri zorla aynıymış gibi yapmanın, aynı şeyi kopyalayıp sonradan farklılaştırmaktan daha acı verici olduğunu söylüyor; ben de katılıyorum. İkincisinde aynı değişikliği iki kez yaparsınız ya da soyutlama getiren bir refactor yaparsınız, ama ilkinde soyutlamanın üstüne eklemeye devam etmeniz veya geri almanız gerekir
      Özellikle yerelliği (locality) bozar; değişiklik yaparken gerçekten önemli olan tek özellik de budur. Sadece bu değişikliği yapmak ve sistemin alakasız kısımlarında yan etki olur mu diye endişelenmek istemiyorum
    • Aşırı baskı nedeniyle yazılım iki doğruluk kaynağına itilmişse, iki kaynak uyuşmazsa main'e merge edilmesini engelleyen bir CI testi eklemek oldukça işe yarar bir yöntemdir
      Bunun tipik örneği pyproject.toml / requirements.txt senkronizasyonudur ve gerçekten en iyi seçenek olduğu durumlar vardır; daha geniş alanlara da uygulanabilir gibi görünüyor. Ön kabul, zaten tek bir doğruluk kaynağının imkânsız olacağı kadar işlerin raydan çıkmış olmasıdır; tedaviden çok hasarı azaltmaya benzer
    • “Birbirinden saparsa bug olur” ölçütü çok iyi bir deneyim kuralıdır
      Belli bir anda iki kod parçası benzer göründüğü için aşırı soyutladığım, sonra da zamanla ayrıştıklarını gördüğüm durumları sık yaşadım
    • Teoride doğru, ama pratikte her türlü tekrardan ne pahasına olursa olsun kaçınmaya çalışan çok insan var
      Özellikle junior geliştiriciler bazen tekrarın bütün kötülüklerin kaynağıymış gibi davranıyor
  • Bazen bu sorunu düşünüyorum. Yakın zamanda kişisel bir projede RTS birimleri için 2D sprite'larla uğraşırken karşıma çıktı; birim sprite'ları sprite sheet içine tutarlı bir şekilde yerleştirilmişti: 8 yönde 5 sprite, bunların 3 yönü mirror ediliyor ve sıra stand, move, attack, die şeklindeydi
    Bu yüzden action + direction alıp oynatılacak sprite dizisini veren bir loader yaptım
    Ama sonra yönü olmayan patlama sprite'ları, 4 yön ve yalnızca 2 mirror içeren ceset sprite'ları ve üstelik ilk dördü dışında orklar ile insanların büyük ölçüde aynı şeyleri paylaştığı durumlar çıktı
    Tüm bunların ortak soyutlamasının ne olduğunu kısa süre düşündüm ama sonunda yükleme kodunun yalnızca bir kısmını ayırdım, sonra da UnitLoader, CorpseLoader ve EffectLoader oluşturdum. Üç loader da biraz benzer şeylerle uğraştığı için daha iyi bir soyutlama olabilir, ama varsa da sonra bulunur. Şimdi tüm durumları ele almaya çalışan karmaşık bir EverythingLoader yapmak yerine, ileride zamanı gelince tekrarı kaldırmak daha kolay

    • “Her şey olabildiğince basit olmalı, ama bundan daha basit olmamalı” sözünü seviyorum
      Programlamada genelleme yoluyla kodu basitleştirmeye yönelik bir içgüdü var, ama gerçek dünya dağınık olduğu için çoğu zaman aşırı basitleştiriyoruz. Metinde dendiği gibi, zaman geçip yeni gereksinimler çıktığında bunun fazla erken yapılmış bir basitleştirme olduğu ortaya çıkıyor
      “Erken soyutlama birçok çirkinliğin kaynağıdır” diye bir özdeyiş rahatlıkla olabilir
    • Ortak soyutlama muhtemelen zaten ayrılmıştır. Tek bir sprite'ın piksellerini yükleyip ekranda gösteren kod bunun örneği olabilir
      Bunun üst katmanında, yani sprite sheet yerleşimini yorumlama ve oynatma modu işleme kısmında çeşitli varyasyonlar var ve her duruma uyan ortak bir soyutlama olmayabilir
      Görünmeyen bir soyutlamayı zorla yaratmaya ya da kusurlu bir soyutlamaya uydurmaya çalışmak yerine, şu an yaptığınız gibi davranmayı tercih ederim. Soyutlama tamamen netleşip ihtiyaç da açıkça ortaya çıkana kadar beklemek iyi bir şeydir
      DRY'ın karşı tarafında WET vardır. Anlamı, her şeyi iki ya da üç kez yazmaktır. Daha da önemlisi, bence yalnızca gerçekten kanıtlanmış kullanım senaryoları için — genellikle önce tekrar olarak ortaya çıkan şeyler için — soyutlama yapılmalıdır. Henüz var olmayan gelecekteki kullanım senaryoları için yazılan kod, gerçekten sahip olduğunuz şeyi soyutlamayı sık sık zorlaştırır; bu olduğunda da durum biraz komik olur
    • Bu yaklaşım doğru. Oyun yapmak en başta eğlenceli olmalı
      Zor ve sıkıcı işler, projenin son %10'una geldiğinizde de yapılabilir
      Üstelik tekrarın yarattığı “bug” bazen oyuncuların sevdiği eğlenceli bir özelliğe bile dönüşebilir
  • OOP kullandığım dönemde soyutlamalar yüzünden çok zorlandım, ama neredeyse saf fonksiyonel yaklaşıma geçtikten sonra kod tekrarı nadirleşti
    Sadece bir fonksiyon yazıp iki yerde çağırmanız yeterli. Asıl soyutlama sorunu veri yapılarında oluyor ama TypeScript interface'leri özünde duck typing olduğundan burada da çok büyük bir sorun yaşanmıyor
    Bu yüzden soyutlama sorunlarından kaynaklanan kod tekrarı nadir. Geliştiricilerin silo hâline gelmesinden doğan kod tekrarı ise çok daha yaygın

    • Hobi olarak fonksiyonel diller kullanıyorum ve akılda tutulması gereken asıl şeyin teknikler olduğunu düşünüyorum
      Modern dillerin çoğu fonksiyonel programlama teorisinin üstüne rahatça kurulabilir ve Haskell bilmek şart değil. Herkesin zihni farklı çalışır ama küçük, basit ve bazen esnek parçaların bütünü oluşturduğu fikri bana çok uygun geliyor
      Bu, büyük, karmaşık ve her şeyi yapan bir şekil dönüştürme makinesinin tam tersi
    • Kod tekrarının ortaya çıkması için geliştiricilerin mutlaka silo hâline gelmesi gerekmez
      Takım büyüklüğü belli bir seviyeyi geçip herkesin diğer herkesin ne yaptığını bilmesi imkânsız hâle geldiğinde kod tekrarı oldukça kaçınılmazdır. Herkes fonksiyonel tarzda yazsa bile bu değişmez
      Nitekim geçen ay şirkette tam da böyle bir şey oldu. Yeni bir saf helper fonksiyonu yazıp dosyanın başına koydum; bir hafta sonra bir iş arkadaşım, fiilen aynı işi yapan ama imzası farklı benzer bir helper fonksiyonunun aynı dosyanın sonunda zaten bulunduğunu söyledi
    • “Fonksiyonu iki farklı yerden çağırmak” derken tam olarak ne kastedildiğini merak ediyorum
  • Metinle aynı bağlamda, ikisini de yaşamış olan herkes buna katılacaktır. Eksik tasarlanmış bir kod tabanı, aşırı tasarlanmış bir kod tabanından çok daha kolay yönetilir

  • Bakımını yapmak zorunda kaldığım en kötü kod, DRY ilkesine uymaya çalışan koddu. Ama bu ilkenin asıl niyetini anlamaya çalışmamışlardı.
    O keşmekeşten çıkmanın tek yolu, geniş ölçekte kod tekrarını yeniden devreye sokmaktı

    • Sorun değil, merak etmeyin; yeni kullanım senaryosunu desteklemek için yeniden kullanılan fonksiyona birkaç muğlak boolean parametresi daha ekleyip dağıtıma çıkarın yeter
    • Önemli olan “denemiş” olmak. Bir süre böyle gidiliyor, sonra da soyutlamanın yanlış olduğu ve artık ona sadık kalınamayacak bir noktaya varılıyor
  • Burada aklıma iki sunum geliyor: Mike Acton’ın Data-Oriented Design and C++ [1] ve Brian Cantrill’in The Complexity of Simplicity [2]
    Mike’ın sunumu, koddaki çözümün gerçek dünyayı modellemek zorunda olmadığını; farklı verilerin farklı problemler oluşturduğunu ve bu yüzden farklı çözümler gerektirdiğini söylüyor. Sunumu yeterince iyi aktarmam zor ama beni çok etkilemişti
    Brian’ın sunumu ise genel olarak soyutlamaları ve “doğru” soyutlamayı bulmanın ne kadar zor olduğunu ele alıyor

    1. https://www.youtube.com/watch?v=rX0ItVEVjHc
    2. https://www.youtube.com/watch?v=Cum5uN2634o
    • Oldukça zeki mühendislerin bile bazen kod tabanının gerçek ihtiyaçlarından çok gerçek dünya metaforlarını öncelemesi bana hep tuhaf gelmiştir.
      Okuldan mezun oluşumun üzerinden daha birkaç yıl geçmişken Rust ile bir connection pool uyguluyordum; en mantıklı uygulama, connection nesnesinin pool’a zayıf bir referans tutması ve drop olduğunda otomatik olarak geri dönmesiydi.
      Çok deneyimli bir yönetici olan müdürüm, “kitaplığı elinde tutan kitaptır değil, kitaplığı tutan kitaplık olur” diyerek bu fikirden hoşlanmamıştı. Tasarımı değiştirecek kadar ikna edici bir gerekçe gibi gelmemişti ama o, bu problemi o metaforun merceği dışında ele almak istemiyordu.
      Sonunda başka bir yönetici, “Kütüphane kitabı kütüphaneyi içermez ama arkasında iade edileceği yeri gösteren kütüphane adı damgası vardır” deyince kilit açıldı. Görünüşe göre o yönetici bu benzetmeyi genişletmeyi makul bulmuştu
      Daha deneyimli olsaydım, konunun özünden taviz vermeden o metaforun içinde konuşmanın yolunu bulabilirdim belki; ama bugün bile, kodun soyutlamasını ve kütüphaneyi kullanma deneyiminin sonuçlarını tartmak yerine o metaforu standart çerçeve diye dayatmasını tamamen tuhaf buluyorum
  • Kimse dinlemek istemiyor. Gerçekten kimse dinlemiyor. Şirketlerin %90’ında, yeni soyutlamalar üretirken mest olan sözde kıdemli geliştiriciler vardır
    Aşırı tasarım, soyutlama ve erken optimizasyon, mühendisliğin üç büyük felaketidir
    Öte yandan bunlar yüzünden her zaman iş olacak olması da sevindirici

    • Kubernetes, mühendis sayısından fazla mikroservis, birkaç bayt ek yükten tasarruf etmek için karmaşık protokoller, her şeyi cloud’a taşımak ve aslında basit bir fonksiyon olabilecek sayısız sınıf bunun tipik örnekleri
  • Benzer şekilde, bazı geliştiriciler satır içi string’lerin veya sayısal sabitlerin tamamen şeytan işi olduğunu sanıyor gibi. Bir PR’da şunu görmüştüm
    HTTPS_SCHEME = 'https'
    DOMAIN = 'www.example.com'
    url = HTTPS_SCHEME + '://' + DOMAIN
    “Sabit değer gömmeyin” lafını cargo cult gibi tekrar etmek dışında bunun ne kazandırdığını anlayamıyorum. Üstelik sabit tanımları dosyanın en üstündeydi, URL’yi oluşturan kod ise yüzlerce satır aşağıdaydı

    • Kodda yakınlığı çok severim. Tanımı mümkün olduğunca kullanıldığı yere yakın yapmayı tercih ederim. Bu gerçekten sinir bozucu bir alışkanlık
      Regex’leri de dosyanın tepesine koymak yerine kullanıldığı yerde bırakabilirsiniz. Dil yeterince akıllıysa muhtemelen bunun sabit olduğunu zaten anlar
      Çok küçük bir fonksiyonsa doğrudan lambda kullanın. Bir ya da iki kez kullanılan tek satırlık fonksiyonların çok uzakta tanımlanmasını istemem
    • Sabitleri en üste koymak özelleştirmeyi daha kolay hale getirebilir. Özellikle bu dosya kopyalanacaksa daha da anlamlıdır
      Test veya staging ortamında https yerine http kullanmanız gerekecekse, şemayı ve domain’i ayırmak ve sabitleri üstte ya da ayrı bir dosyada tutmak mantıklıdır. url’nin birçok yerde mi yoksa tek bir yerde mi oluşturulduğu da önemlidir
      Dosyanın tepesinde isimlendirilmiş sabitler tutmak son derece yaygın bir stildir ve bazen takımın kodlama standartlarının bir parçasıdır
      Başka nedenler de olabilir; bu yüzden Chesterton’s Fence ilkesini hatırlamak iyi olur. Her hâlükârda buna doğrudan cargo cult demek iyi bir fikir değil. Bir başkası da satır içi literal kullanmayı aynı şekilde cargo cult sayabilir. Tuhaf görünüyorsa sorabilirsiniz; iyi bir sebebi olabilir ya da kimse önemsememiştir, siz de refactor edip sabitleri satır içine alırsanız memnun bile olabilirler
    • Ben de buna benzer şeyler yaşadım. Bir Event’in adı varsa, devasa bir monolith’te ya da bir mikroservis depo kümesinin tamamında doğrudan grep yapıp o event ile ilgili tüm dosyaları bulabilirsiniz
      Onu sabite çıkarırsanız, sonra projeleri tek tek açıp kullanımları bul yapmak zorunda kalırsınız
  • Mikroservis kullanırsanız ikisini de yapabilirsiniz

    • Şaka olduğunu biliyorum ama ideal dünyadaki mikroservislerde servisler arası kod tekrarı diye bir kavram yoktur
      Bir servisin bakımından sorumluysanız, başka bir servisteki kodu umursamanız için hiçbir neden yoktur. Başka bir ekibin koduysa neden umursayasınız? Hatta o ekibin var olduğunu bile bilmeniz gerekmez. Büyük sistemlerde, var olan tüm uygulamaları bilmek zaten gerçekçi olmayabiliyor
    • Ama durun! Daha fazlası var!
      Yalnızca $19.95 karşılığında tek bir single point of failure’ı birden fazla single point of failure’a dönüştürüyoruz!
    • 10 durumun 9’unda mikroservisler birbirine aşırı bağımlı hale gelip dağıtık monolithe dönüşüyor
      Servis odaklı mimari kullanıp sadece monolith dağıtmak daha iyidir. Test etmesi daha kolaydır ve serialization/deserialization gibi ek katmanlardan da kaçınırsınız
  • Çoğu kıdemli geliştiricinin DRY'yi körü körüne izlememek gerektiğini bildiğini düşünüyorum. Yine de çoğumuz, birden fazla yinelenen kod kaynağını sürdürmek zorunda olma fikrinden rahatsız oluruz
    Bunu ele almak için, iki çağırıcının ortak koda bağımlı olduğu basit modeli dikkatle incelemek gerekir. Eğer ortak kod, yalnızca bir çağırıcının ihtiyaçları yüzünden değişmek zorundaysa, o kod ortak olana ait değildir
    DRY'nin yanlış hedefi, kapsülleme ile çözmeye çalıştığı şeydir. Kapsülleme, yeniden düzenleme işini çağırıcılardan ortak koda taşır. Ancak ortak kodu güncellemenin etkisi, çağırıcılara kıyasla çok daha büyük olduğundan bu istenen yön değildir
    Kapsüllemeden kaçınırken de DRY'ye uyulabilir. Çağıranın farkında olması gereken birden fazla ince soyutlama bulundurmak daha iyidir. OOP'de bunun için SRP ve IoC öğrenilir; prosedürel programlamada ise bir dizi yardımcı fonksiyon çağırmak şeklinde doğal olarak ortaya çıkar