İçeriğe geç

Code Smell ve Refactoring — Davranışı Değiştirmeden Değiştirmek

Orta 8 dk Çok sık karşılaşılır

Önce şunu oku: Temiz Kod — İsimler, Fonksiyonlar, Yorumlar

30 saniyede özet

Code smell bir hata değil, 'buraya bir bak' diyen bir ipucudur. Refactoring kodun davranışını aynı bırakıp düzenini değiştirmektir; aynı kaldığını testler kanıtlar. Küçük adım at, her adımda test et.

“Sadece biraz topladım” denilen bir değişiklik yayına alındı. Bir hafta sonra muhasebe bazı faturalarda bir kuruş fark buldu. Temizlik, fark ettirmeden bir davranışı değiştirmişti.

Küçük adım ve her adımda test, ya da tek büyük sıçrama.
Adım adım oku
  1. Önce kodun bugün ne yaptığını olduğu gibi sabitleyen testler yazılır: bir güvenlik ağı.
  2. Her küçük düzenlemeden sonra testler çalışır ve yeşil kalır. Sonra bir adım daha.
  3. Tek büyük sıçramada isim, yuvarlama ve sıralama birlikte değişir. Test kırmızı, ama hangisi yüzünden?
  4. Küçük adımlarda kırmızı test tek bir değişikliği gösterir; sebep hemen bellidir.
  1. Bayt: Sadece biraz topladım, davranışa hiç dokunmadım. Yayına aldık.

  2. Sen: Bir hafta sonra muhasebe aradı: bazı faturalarda bir kuruş fark varmış.

  3. Bayt: Nasıl yani? Ben sadece düzenlemiştim!

  4. Bayt: Davranışın aynı kaldığını neyle kanıtladın? Asıl soru bu.

Koku bir ipucudur, hata değil

Bir code smellKodda daha derin bir tasarım sorununa işaret edebilen, kendisi hata olmayan bir belirti: uzun metot, tekrar, anlamsız isim. Teşhis değil, bakılacak yeri gösteren bir ipucu.Sözlükte gör → kendisi hata değildir. Daha derin bir tasarım sorununun nerede olabileceğini gösterir.

SmellBelirtiGenelde çözümü
Uzun metotYorumlarla bölümlere ayrılmış gövdeExtract Method
feature envyBir metodun kendi sınıfından çok başka bir sınıfın verisiyle uğraşması. Genelde o davranışın verinin durduğu sınıfa taşınması gerektiğini gösterir.Sözlükte gör →Başka sınıfın getter’larıyla dolu metotMove Method
Primitive obsessionHep birlikte gezen amount ve currencyDeğer nesnesi
Anlamsız isimd, tmp, data2Rename
Kafam karıştı, daha basit anlat

Code smell, buzdolabından gelen tuhaf bir koku gibidir. Kokunun kendisi sorun değildir, ama içeride bir şeyin bozulduğunu haber verir.

Hızlı kontrolBaşlangıç

Refactoring'i herhangi bir kod değişikliğinden ayıran şey nedir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Her smell'i genelde onu gideren refactoring ile eşleştir.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Extract Method

Bir parçayı adı olan bir metoda çıkar

    Move Method

    Davranışı verinin durduğu yere taşı

      Değer nesnesi / parametre nesnesi

      Birlikte gezen değerleri bir tipe topla

        Rename

        Adı niyeti anlatsın

          Davranış aynı kalır

          refactoringKodun dışarıdan görülen davranışını değiştirmeden iç yapısını küçük, güvenli adımlarla değiştirmek. Davranış değişiyorsa artık refactoring değildir.Sözlükte gör → yapıyı değiştirir, davranışı değil. Bu bir iddiadır ve iddiayı doğrulayan şey testlerdir.

          Para hesabını double'dan BigDecimal'e geçirdin ve HALF_EVEN yuvarlama seçtin. Kod daha doğru. Bu bir refactoring mi? Cevabı göster

          Hayır. 10,125 TL eskiden 10,13 idi, şimdi 10,12. Sonuç değişiyorsa davranış değişmiştir — belki doğru bir değişiklik, ama ayrı ve bilinçli yapılmalı.

          Testi olmayan kodda ilk adım, bugünkü davranışı kaydeden bir karakterizasyon testiLegacy kodun doğru olduğunu değil, şu an ne yaptığını kaydeden test. Yanlış davranışı da sabitler — amaç değişikliğin neyi bozduğunu görebilmektir.Sözlükte gör → yazmaktır. Doğruyu değil, olanı sabitler.

          Kafam karıştı, daha basit anlat

          Refactoring, odayı toplamaktır: eşyalar yer değiştirir ama hiçbiri kaybolmaz. Testler, toplarken hiçbir şeyin kaybolmadığını gösteren listedir.

          Hızlı kontrolOrta

          Para hesaplarında `double`'dan `BigDecimal`'e geçerken yuvarlama modu da değişti. Bu değişiklik bir refactoring midir?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Testi olmayan eski bir sınıfı değiştirmen gerekiyor. İlk adım ne olmalı?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Kendin gör

          Refactoring — küçük adımlar ve güvenlik ağı

          Tohum 846929
          • checkout() satır48
          • Kalan smell4
          • Doğrulanmamış değişiklik0
          1. Oynat ya da adımla.
          Hız
          Adım 0

          Şu an ne oldu?

          checkout(): 48 satır, 4 smell

          Dört temizlik hamlesi planlandı. Biri masum görünüyor ama bir sonucu 1 kuruş değiştiriyor.

          Görevler0/3

          • Bir davranış değişikliğini "temizlik" diye yayına çıkaraçık

            İpucu

            Kimsenin fark etmesi için bir mekanizma olmasın.

          • Testler kırmızı olsun ama hangi değişikliğin bozduğu belli olmasınaçık

            İpucu

            Güvenlik ağı var, ama değişiklikler tek bir büyük adımda.

          • Davranış değişikliğini yaptığın anda yakalaaçık

            İpucu

            Test + her hamleden sonra çalıştırmak.

          Olay günlüğü (0)

          Henüz olay yok. Oynat veya adımla.

          1. Varsayılanla oynat. Test yok, hepsi tek seferde: kod temiz, kuruş farkı yayında.
          2. Testleri aç. Kırmızı — ama dört değişiklikten hangisi?
          3. “Her adımdan sonra test” seç. Kırmızı yalnızca dördüncü adımda, suçlu belli.
          4. Testleri kapat, küçük adımları bırak. Adım küçük ama kimse bakmıyor.
          Hızlı kontrolOrta

          Refactoring'de "küçük adımlar" neden bu kadar vurgulanır?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Satır satır: kuralı verinin yanına taşımak

          Feature envy'den Move Method'a

          OrderService.java
          1BigDecimal discountFor(Order order) {
          2 var customer = order.getCustomer();
          şu an çalışan satır if (customer.getTier() == GOLD && customer.getYears() > 3) {
          4 return order.getTotal().multiply(RATE_15);
          5 }
          6 if (customer.getTier() == GOLD) {
          7 return order.getTotal().multiply(RATE_10);
          8 }
          9 return BigDecimal.ZERO;
          10}

          Debug

          Adım 1/5

          okuyucu Karar tamamen Customer'ın verisine dayanıyor. OrderService bu kuralı neden biliyor?

          Java 21UTF-8LF3:1

          Sol/sağ ok tuşlarıyla da gezebilirsin.

          Her hamle tek başına ve testle doğrulandı. Sonuç aynı; değişen yalnızca kuralın nerede durduğu.

          Aynı hamleyi bir bankanın EFT ücretiyle deneyelim. Önce bugünkü ücretleri bir testle sabitliyoruz, sonra kuralı hesabın yanına taşıyoruz; test baştan sona yeşil kalıyor.

          Derinleş · EFT ücreti: kuralı taşı, kuruşu koru 4 dosya · ~68 satır · ilk okumada atlayabilirsin
          Proje dosyaları

          src/test/java/com/bank/fee/ EftFeeCharacterizationTest.java Bugünkü ücretleri olduğu gibi sabitleyen karakterizasyon testi: doğruyu değil, olanı kaydeder.

          src/test/java/com/bank/fee/EftFeeCharacterizationTest.java
          // Pins what the fee code does today, before anyone tidies it.
          // Expected values were copied from the current output, not from a spec.
          class EftFeeCharacterizationTest {
          private final EftFeeCalculator calculator = new EftFeeCalculator();
          @ParameterizedTest
          @CsvSource({
          // segment, years, amount, fee
          "STANDARD, 1, 10000.00, 25.00",
          "STANDARD, 1, 500.00, 2.50", // the minimum fee applies
          "PREMIUM, 3, 10000.00, 15.00", // exactly 3 years is not loyal yet
          "PREMIUM, 5, 12345.00, 12.35", // 12.345 is rounded up today
          })
          void feeStaysExactlyAsToday(Segment segment, int years, BigDecimal amount, BigDecimal fee) {
          Account account = new Account("TR330006100519786457841326", segment, years);
          assertThat(calculator.feeFor(account, amount)).isEqualTo(fee);
          }
          }

          src/main/java/com/bank/account/ Account.java Ücret oranı kuralı, yalnızca kendi verisini okuduğu hesaba taşındı.

          src/main/java/com/bank/account/Account.java
          public class Account {
          private static final BigDecimal STANDARD_RATE = new BigDecimal("0.0025");
          private static final BigDecimal PREMIUM_RATE = new BigDecimal("0.0015");
          private static final BigDecimal LOYAL_PREMIUM_RATE = new BigDecimal("0.0010");
          private final String iban;
          private final Segment segment;
          private final int yearsAsCustomer;
          public Account(String iban, Segment segment, int yearsAsCustomer) {
          this.iban = iban;
          this.segment = segment;
          this.yearsAsCustomer = yearsAsCustomer;
          }
          // Moved here from EftFeeCalculator: the rule only reads this account's own data.
          public BigDecimal eftFeeRate() {
          if (segment == Segment.PREMIUM && yearsAsCustomer > 3) {
          return LOYAL_PREMIUM_RATE;
          }
          if (segment == Segment.PREMIUM) {
          return PREMIUM_RATE;
          }
          return STANDARD_RATE;
          }
          }

          src/main/java/com/bank/fee/ EftFeeCalculator.java Hesaplayıcı artık kısa; yuvarlama bilerek bugünkü gibi bırakıldı.

          src/main/java/com/bank/fee/EftFeeCalculator.java
          // Domestic EFT fees, charged in TRY.
          public class EftFeeCalculator {
          private static final BigDecimal MIN_FEE = new BigDecimal("2.50");
          public BigDecimal feeFor(Account account, BigDecimal amount) {
          return amount.multiply(account.eftFeeRate())
          .setScale(2, RoundingMode.HALF_UP) // today's rounding, kept on purpose
          .max(MIN_FEE);
          }
          }

          tidy-eft-fees.diff Şöyle de yazılabilirdi: taşıma ve yuvarlama aynı commit'te. Bak, test 12,35 yerine 12,34 görüyor.

          tidy-eft-fees.diff
          --- a/src/main/java/com/bank/fee/EftFeeCalculator.java
          +++ b/src/main/java/com/bank/fee/EftFeeCalculator.java
          @@ -7,5 +7,5 @@ public class EftFeeCalculator {
          public BigDecimal feeFor(Account account, BigDecimal amount) {
          - return amount.multiply(rateFor(account))
          - .setScale(2, RoundingMode.HALF_UP)
          + return amount.multiply(account.eftFeeRate())
          + .setScale(2, RoundingMode.HALF_EVEN)
          .max(MIN_FEE);
          }
          Kafam karıştı, daha basit anlat

          Her küçük adımdan sonra testleri çalıştır. Bir şey kırılırsa hangi adımın kırdığını hemen bilirsin.

          Hızlı kontrolOrta

          Bu metotta en belirgin smell hangi satırlarda?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Hatalı satıra dokun, sonra kontrol et.

          OrderService.java
          Java 21UTF-8LF

          Tuzaklar

          Refactoring ve özellik aynı commit’te. Kırmızı test ya da hata raporu hangisinden geldiğini söylemez.

          Metin olarak bul-değiştir. İlgisiz yerler değişir, bazı kullanımlar kaçar. IDE’nin Rename’i sembolü bilir.

          Baştan yazmak. Eski kodun belgelenmemiş davranışları sessizce kaybolur.

          Smell’i kural sanmak. Okunaklı bir 12 satırı, adı olmayan parçalara bölmek kodu iyileştirmez.

          Hızlı kontrolOrta

          Bir alanın adını değiştirmek için projede metin olarak bul-değiştir yapıldı. Asıl risk ne?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Kendini sına

          Şimşek turu1/5

          Refactoring, kodun davranışını aynı bırakıp düzenini değiştirmektir.

          Soru 1/2İleri

          Ekip, 6 ay sürecek bir "baştan yazma" önerisini tartışıyor. En güçlü karşı argüman hangisi?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Aklında kalacak üç şey

          1. 1 Refactoring davranışı değiştirmez. Sonucu değiştiren bir temizlik, adı ne olursa olsun bir davranış değişikliğidir.
          2. 2 Test olmadan refactoring bir umuttur. Testi olmayan kodda ilk adım, bugünkü davranışı olduğu gibi sabitleyen testler yazmaktır.
          3. 3 Adım ne kadar küçükse kırmızı test o kadar net konuşur. Düzeni ve davranışı aynı commit'te değiştirmek hatanın kaynağını gizler.
          Sonraki kapı Bir nesnenin bir sürü isteğe bağlı ayarı varsa, onu hatasız nasıl kurarsın? Yaratımsal Kalıplar — Constructor Ne Zaman Yetmez · 10 dk

          5 kart sonraki derste seni bekliyor

          0/5 kart bu dersten toplandı