Yordamdan Nesneye Dayalı Kod'a Dönüştür


16

Büyük bir ASP.NET webforms uygulamasının mevcut kod tabanını temizlemeye nasıl başlayacağınıza ilişkin stratejileri öğrenmek amacıyla Eski Kod ve Temiz Kod ile Etkili Çalışma konusunu okuyorum .

Bu sistem 2005'ten beri var ve o zamandan beri bir takım geliştirmeler yapıldı. Başlangıçta kod aşağıdaki gibi yapılandırılmıştır (ve yine de büyük ölçüde bu şekilde yapılandırılmıştır):

  • ASP.NET (aspx / ascx)
  • Arkada kod (c #)
  • İş Mantık Katmanı (c #)
  • Veri Erişim Katmanı (c #)
  • Veritabanı (Oracle)

Asıl mesele, kodun nesne yönelimli olarak yordamsal işlem görmesidir. Her iki kitapta açıklanan tüm yönergeleri neredeyse ihlal ediyor.

Bu, İş Mantık Katmanındaki tipik bir sınıfa örnektir:

    public class AddressBO
{
    public TransferObject GetAddress(string addressID)
    {
        if (StringUtils.IsNull(addressID))
        {
            throw new ValidationException("Address ID must be entered");
        }

        AddressDAO addressDAO = new AddressDAO();
        return addressDAO.GetAddress(addressID);
    }

    public TransferObject Insert(TransferObject addressDetails)
    {
        if (StringUtils.IsNull(addressDetails.GetString("EVENT_ID")) ||
            StringUtils.IsNull(addressDetails.GetString("LOCALITY")) ||
            StringUtils.IsNull(addressDetails.GetString("ADDRESS_TARGET")) ||
            StringUtils.IsNull(addressDetails.GetString("ADDRESS_TYPE_CODE")) ||
            StringUtils.IsNull(addressDetails.GetString("CREATED_BY")))
        {
            throw new ValidationException(
                "You must enter an Event ID, Locality, Address Target, Address Type Code and Created By.");
        }

        string addressID = Sequence.GetNextValue("ADDRESS_ID_SEQ");
        addressDetails.SetValue("ADDRESS_ID", addressID);

        string syncID = Sequence.GetNextValue("SYNC_ID_SEQ");
        addressDetails.SetValue("SYNC_ADDRESS_ID", syncID);

        TransferObject syncDetails = new TransferObject();

        Transaction transaction = new Transaction();

        try
        {
            AddressDAO addressDAO = new AddressDAO();
            addressDAO.Insert(addressDetails, transaction);

            // insert the record for the target
            TransferObject addressTargetDetails = new TransferObject();
            switch (addressDetails.GetString("ADDRESS_TARGET"))
            {
                case "PARTY_ADDRESSES":
                    {
                        addressTargetDetails.SetValue("ADDRESS_ID", addressID);
                        addressTargetDetails.SetValue("ADDRESS_TYPE_CODE",
                                                      addressDetails.GetString("ADDRESS_TYPE_CODE"));
                        addressTargetDetails.SetValue("PARTY_ID", addressDetails.GetString("PARTY_ID"));
                        addressTargetDetails.SetValue("EVENT_ID", addressDetails.GetString("EVENT_ID"));
                        addressTargetDetails.SetValue("CREATED_BY", addressDetails.GetString("CREATED_BY"));

                        addressDAO.InsertPartyAddress(addressTargetDetails, transaction);

                        break;
                    }
                case "PARTY_CONTACT_ADDRESSES":
                    {
                        addressTargetDetails.SetValue("ADDRESS_ID", addressID);
                        addressTargetDetails.SetValue("ADDRESS_TYPE_CODE",
                                                      addressDetails.GetString("ADDRESS_TYPE_CODE"));
                        addressTargetDetails.SetValue("PUBLIC_RELEASE_FLAG",
                                                      addressDetails.GetString("PUBLIC_RELEASE_FLAG"));
                        addressTargetDetails.SetValue("CONTACT_ID", addressDetails.GetString("CONTACT_ID"));
                        addressTargetDetails.SetValue("EVENT_ID", addressDetails.GetString("EVENT_ID"));
                        addressTargetDetails.SetValue("CREATED_BY", addressDetails.GetString("CREATED_BY"));

                        addressDAO.InsertContactAddress(addressTargetDetails, transaction);

                        break;
                    }

                << many more cases here >>
                default:
                    {
                        break;
                    }
            }

            // synchronise
            SynchronisationBO synchronisationBO = new SynchronisationBO();
            syncDetails = synchronisationBO.Synchronise("I", transaction,
                                                        "ADDRESSES", addressDetails.GetString("ADDRESS_TARGET"),
                                                        addressDetails, addressTargetDetails);


            // commit
            transaction.Commit();
        }
        catch (Exception)
        {
            transaction.Rollback();
            throw;
        }

        return new TransferObject("ADDRESS_ID", addressID, "SYNC_DETAILS", syncDetails);
    }


    << many more methods are here >>

}

Çoğaltılması çoktur, sınıfın çeşitli sorumlulukları vardır, vb. - sadece 'temiz' kodudur.

Sistemdeki kodun tamamı somut uygulamalara bağlıdır.

Bu, Veri Erişim Katmanı'ndaki tipik bir sınıfa bir örnektir:

    public class AddressDAO : GenericDAO
{
    public static readonly string BASE_SQL_ADDRESSES =
        "SELECT " +
        "  a.address_id, " +
        "  a.event_id, " +
        "  a.flat_unit_type_code, " +
        "  fut.description as flat_unit_description, " +
        "  a.flat_unit_num, " +
        "  a.floor_level_code, " +
        "  fl.description as floor_level_description, " +
        "  a.floor_level_num, " +
        "  a.building_name, " +
        "  a.lot_number, " +
        "  a.street_number, " +
        "  a.street_name, " +
        "  a.street_type_code, " +
        "  st.description as street_type_description, " +
        "  a.street_suffix_code, " +
        "  ss.description as street_suffix_description, " +
        "  a.postal_delivery_type_code, " +
        "  pdt.description as postal_delivery_description, " +
        "  a.postal_delivery_num, " +
        "  a.locality, " +
        "  a.state_code, " +
        "  s.description as state_description, " +
        "  a.postcode, " +
        "  a.country, " +
        "  a.lock_num, " +
        "  a.created_by, " +
        "  TO_CHAR(a.created_datetime, '" + SQL_DATETIME_FORMAT + "') as created_datetime, " +
        "  a.last_updated_by, " +
        "  TO_CHAR(a.last_updated_datetime, '" + SQL_DATETIME_FORMAT + "') as last_updated_datetime, " +
        "  a.sync_address_id, " +
        "  a.lat," +
        "  a.lon, " +
        "  a.validation_confidence, " +
        "  a.validation_quality, " +
        "  a.validation_status " +
        "FROM ADDRESSES a, FLAT_UNIT_TYPES fut, FLOOR_LEVELS fl, STREET_TYPES st, " +
        "     STREET_SUFFIXES ss, POSTAL_DELIVERY_TYPES pdt, STATES s " +
        "WHERE a.flat_unit_type_code = fut.flat_unit_type_code(+) " +
        "AND   a.floor_level_code = fl.floor_level_code(+) " +
        "AND   a.street_type_code = st.street_type_code(+) " +
        "AND   a.street_suffix_code = ss.street_suffix_code(+) " +
        "AND   a.postal_delivery_type_code = pdt.postal_delivery_type_code(+) " +
        "AND   a.state_code = s.state_code(+) ";


    public TransferObject GetAddress(string addressID)
    {
        //Build the SELECT Statement
        StringBuilder selectStatement = new StringBuilder(BASE_SQL_ADDRESSES);

        //Add WHERE condition
        selectStatement.Append(" AND a.address_id = :addressID");

        ArrayList parameters = new ArrayList{DBUtils.CreateOracleParameter("addressID", OracleDbType.Decimal, addressID)};

        // Execute the SELECT statement
        Query query = new Query();
        DataSet results = query.Execute(selectStatement.ToString(), parameters);

        // Check if 0 or more than one rows returned
        if (results.Tables[0].Rows.Count == 0)
        {
            throw new NoDataFoundException();
        }
        if (results.Tables[0].Rows.Count > 1)
        {
            throw new TooManyRowsException();
        }

        // Return a TransferObject containing the values
        return new TransferObject(results);
    }


    public void Insert(TransferObject insertValues, Transaction transaction)
    {
        // Store Values
        string addressID = insertValues.GetString("ADDRESS_ID");
        string syncAddressID = insertValues.GetString("SYNC_ADDRESS_ID");
        string eventID = insertValues.GetString("EVENT_ID");
        string createdBy = insertValues.GetString("CREATED_BY");

        // postal delivery
        string postalDeliveryTypeCode = insertValues.GetString("POSTAL_DELIVERY_TYPE_CODE");
        string postalDeliveryNum = insertValues.GetString("POSTAL_DELIVERY_NUM");

        // unit/building
        string flatUnitTypeCode = insertValues.GetString("FLAT_UNIT_TYPE_CODE");
        string flatUnitNum = insertValues.GetString("FLAT_UNIT_NUM");
        string floorLevelCode = insertValues.GetString("FLOOR_LEVEL_CODE");
        string floorLevelNum = insertValues.GetString("FLOOR_LEVEL_NUM");
        string buildingName = insertValues.GetString("BUILDING_NAME");

        // street
        string lotNumber = insertValues.GetString("LOT_NUMBER");
        string streetNumber = insertValues.GetString("STREET_NUMBER");
        string streetName = insertValues.GetString("STREET_NAME");
        string streetTypeCode = insertValues.GetString("STREET_TYPE_CODE");
        string streetSuffixCode = insertValues.GetString("STREET_SUFFIX_CODE");

        // locality/state/postcode/country
        string locality = insertValues.GetString("LOCALITY");
        string stateCode = insertValues.GetString("STATE_CODE");
        string postcode = insertValues.GetString("POSTCODE");
        string country = insertValues.GetString("COUNTRY");

        // esms address
        string esmsAddress = insertValues.GetString("ESMS_ADDRESS");

        //address/GPS
        string lat = insertValues.GetString("LAT");
        string lon = insertValues.GetString("LON");
        string zoom = insertValues.GetString("ZOOM");

        //string validateDate = insertValues.GetString("VALIDATED_DATE");
        string validatedBy = insertValues.GetString("VALIDATED_BY");
        string confidence = insertValues.GetString("VALIDATION_CONFIDENCE");
        string status = insertValues.GetString("VALIDATION_STATUS");
        string quality = insertValues.GetString("VALIDATION_QUALITY");


        // the insert statement
        StringBuilder insertStatement = new StringBuilder("INSERT INTO ADDRESSES (");
        StringBuilder valuesStatement = new StringBuilder("VALUES (");

        ArrayList parameters = new ArrayList();

        // build the insert statement
        insertStatement.Append("ADDRESS_ID, EVENT_ID, CREATED_BY, CREATED_DATETIME, LOCK_NUM ");
        valuesStatement.Append(":addressID, :eventID, :createdBy, SYSDATE, 1 ");
        parameters.Add(DBUtils.CreateOracleParameter("addressID", OracleDbType.Decimal, addressID));
        parameters.Add(DBUtils.CreateOracleParameter("eventID", OracleDbType.Decimal, eventID));
        parameters.Add(DBUtils.CreateOracleParameter("createdBy", OracleDbType.Varchar2, createdBy));

        // build the insert statement
        if (!StringUtils.IsNull(syncAddressID))
        {
            insertStatement.Append(", SYNC_ADDRESS_ID");
            valuesStatement.Append(", :syncAddressID");
            parameters.Add(DBUtils.CreateOracleParameter("syncAddressID", OracleDbType.Decimal, syncAddressID));
        }

        if (!StringUtils.IsNull(postalDeliveryTypeCode))
        {
            insertStatement.Append(", POSTAL_DELIVERY_TYPE_CODE");
            valuesStatement.Append(", :postalDeliveryTypeCode ");
            parameters.Add(DBUtils.CreateOracleParameter("postalDeliveryTypeCode", OracleDbType.Varchar2, postalDeliveryTypeCode));
        }

        if (!StringUtils.IsNull(postalDeliveryNum))
        {
            insertStatement.Append(", POSTAL_DELIVERY_NUM");
            valuesStatement.Append(", :postalDeliveryNum ");
            parameters.Add(DBUtils.CreateOracleParameter("postalDeliveryNum", OracleDbType.Varchar2, postalDeliveryNum));
        }

        if (!StringUtils.IsNull(flatUnitTypeCode))
        {
            insertStatement.Append(", FLAT_UNIT_TYPE_CODE");
            valuesStatement.Append(", :flatUnitTypeCode ");
            parameters.Add(DBUtils.CreateOracleParameter("flatUnitTypeCode", OracleDbType.Varchar2, flatUnitTypeCode));
        }

        if (!StringUtils.IsNull(lat))
        {
            insertStatement.Append(", LAT");
            valuesStatement.Append(", :lat ");
            parameters.Add(DBUtils.CreateOracleParameter("lat", OracleDbType.Decimal, lat));
        }

        if (!StringUtils.IsNull(lon))
        {
            insertStatement.Append(", LON");
            valuesStatement.Append(", :lon ");
            parameters.Add(DBUtils.CreateOracleParameter("lon", OracleDbType.Decimal, lon));
        }

        if (!StringUtils.IsNull(zoom))
        {
            insertStatement.Append(", ZOOM");
            valuesStatement.Append(", :zoom ");
            parameters.Add(DBUtils.CreateOracleParameter("zoom", OracleDbType.Decimal, zoom));
        }

        if (!StringUtils.IsNull(flatUnitNum))
        {
            insertStatement.Append(", FLAT_UNIT_NUM");
            valuesStatement.Append(", :flatUnitNum ");
            parameters.Add(DBUtils.CreateOracleParameter("flatUnitNum", OracleDbType.Varchar2, flatUnitNum));
        }

        if (!StringUtils.IsNull(floorLevelCode))
        {
            insertStatement.Append(", FLOOR_LEVEL_CODE");
            valuesStatement.Append(", :floorLevelCode ");
            parameters.Add(DBUtils.CreateOracleParameter("floorLevelCode", OracleDbType.Varchar2, floorLevelCode));
        }

        if (!StringUtils.IsNull(floorLevelNum))
        {
            insertStatement.Append(", FLOOR_LEVEL_NUM");
            valuesStatement.Append(", :floorLevelNum ");
            parameters.Add(DBUtils.CreateOracleParameter("floorLevelNum", OracleDbType.Varchar2, floorLevelNum));
        }

        if (!StringUtils.IsNull(buildingName))
        {
            insertStatement.Append(", BUILDING_NAME");
            valuesStatement.Append(", :buildingName ");
            parameters.Add(DBUtils.CreateOracleParameter("buildingName", OracleDbType.Varchar2, buildingName));
        }

        if (!StringUtils.IsNull(lotNumber))
        {
            insertStatement.Append(", LOT_NUMBER");
            valuesStatement.Append(", :lotNumber ");
            parameters.Add(DBUtils.CreateOracleParameter("lotNumber", OracleDbType.Varchar2, lotNumber));
        }

        if (!StringUtils.IsNull(streetNumber))
        {
            insertStatement.Append(", STREET_NUMBER");
            valuesStatement.Append(", :streetNumber ");
            parameters.Add(DBUtils.CreateOracleParameter("streetNumber", OracleDbType.Varchar2, streetNumber));
        }

        if (!StringUtils.IsNull(streetName))
        {
            insertStatement.Append(", STREET_NAME");
            valuesStatement.Append(", :streetName ");
            parameters.Add(DBUtils.CreateOracleParameter("streetName", OracleDbType.Varchar2, streetName));
        }

        if (!StringUtils.IsNull(streetTypeCode))
        {
            insertStatement.Append(", STREET_TYPE_CODE");
            valuesStatement.Append(", :streetTypeCode ");
            parameters.Add(DBUtils.CreateOracleParameter("streetTypeCode", OracleDbType.Varchar2, streetTypeCode));
        }

        if (!StringUtils.IsNull(streetSuffixCode))
        {
            insertStatement.Append(", STREET_SUFFIX_CODE");
            valuesStatement.Append(", :streetSuffixCode ");
            parameters.Add(DBUtils.CreateOracleParameter("streetSuffixCode", OracleDbType.Varchar2, streetSuffixCode));
        }

        if (!StringUtils.IsNull(locality))
        {
            insertStatement.Append(", LOCALITY");
            valuesStatement.Append(", :locality");
            parameters.Add(DBUtils.CreateOracleParameter("locality", OracleDbType.Varchar2, locality));
        }

        if (!StringUtils.IsNull(stateCode))
        {
            insertStatement.Append(", STATE_CODE");
            valuesStatement.Append(", :stateCode");
            parameters.Add(DBUtils.CreateOracleParameter("stateCode", OracleDbType.Varchar2, stateCode));
        }

        if (!StringUtils.IsNull(postcode))
        {
            insertStatement.Append(", POSTCODE");
            valuesStatement.Append(", :postcode ");
            parameters.Add(DBUtils.CreateOracleParameter("postcode", OracleDbType.Varchar2, postcode));
        }

        if (!StringUtils.IsNull(country))
        {
            insertStatement.Append(", COUNTRY");
            valuesStatement.Append(", :country ");
            parameters.Add(DBUtils.CreateOracleParameter("country", OracleDbType.Varchar2, country));
        }

        if (!StringUtils.IsNull(esmsAddress))
        {
            insertStatement.Append(", ESMS_ADDRESS");
            valuesStatement.Append(", :esmsAddress ");
            parameters.Add(DBUtils.CreateOracleParameter("esmsAddress", OracleDbType.Varchar2, esmsAddress));
        }

        if (!StringUtils.IsNull(validatedBy))
        {
            insertStatement.Append(", VALIDATED_DATE");
            valuesStatement.Append(", SYSDATE ");
            insertStatement.Append(", VALIDATED_BY");
            valuesStatement.Append(", :validatedBy ");
            parameters.Add(DBUtils.CreateOracleParameter("validatedBy", OracleDbType.Varchar2, validatedBy));
        }


        if (!StringUtils.IsNull(confidence))
        {
            insertStatement.Append(", VALIDATION_CONFIDENCE");
            valuesStatement.Append(", :confidence ");
            parameters.Add(DBUtils.CreateOracleParameter("confidence", OracleDbType.Decimal, confidence));
        }

        if (!StringUtils.IsNull(status))
        {
            insertStatement.Append(", VALIDATION_STATUS");
            valuesStatement.Append(", :status ");
            parameters.Add(DBUtils.CreateOracleParameter("status", OracleDbType.Varchar2, status));
        }

        if (!StringUtils.IsNull(quality))
        {
            insertStatement.Append(", VALIDATION_QUALITY");
            valuesStatement.Append(", :quality ");
            parameters.Add(DBUtils.CreateOracleParameter("quality", OracleDbType.Decimal, quality));
        }

        // finish off the statement
        insertStatement.Append(") ");
        valuesStatement.Append(")");

        // build the insert statement
        string sql = insertStatement + valuesStatement.ToString();

        // Execute the INSERT Statement
        Dml dmlDAO = new Dml();
        int rowsAffected = dmlDAO.Execute(sql, transaction, parameters);

        if (rowsAffected == 0)
        {
            throw new NoRowsAffectedException();
        }
    }

    << many more methods go here >>
}

Bu sistem, 1 hafta .NET dersinden sonra 2005 yılında benim ve küçük bir ekip tarafından geliştirildi. Daha önce deneyimlerim istemci-sunucu uygulamalarındaydı. Son 5 yılda, otomatik birim testi, otomatik entegrasyon testi ve otomatik kabul testinin (Selenyum veya eşdeğerini kullanarak) faydalarını tanımaya başladım, ancak mevcut kod tabanının bu kavramları tanıtması imkansız görünüyor.

Şimdi sıkı zaman çerçeveleri ile büyük bir geliştirme projesi üzerinde çalışmaya başlıyoruz. Ekip 5 .NET geliştiricisinden oluşur - birkaç yıl .NET deneyimi olan 2 geliştirici ve çok az .NET deneyimi olan veya hiç olmayan 3 geliştirici. Hiçbir ekibin (ben dahil) .NET birim sınaması veya alaycı çerçeveleri kullanma deneyimi yoktur.

Bu kodu daha temiz, daha nesne yönelimli, test edilebilir ve sürdürülebilir hale getirmek için hangi stratejiyi kullanırsınız?


9
Bir yana, sistemi yeniden yazmak için bir maliyet gerekçesi olup olmadığını iki kez kontrol etmek faydalı olabilir. Eski kod çirkin olabilir, ancak yeterince iyi çalışırsa, pürüzlü kenarlara koymak ve geliştirme zamanınızı başka bir yere yatırmak daha ucuz olabilir.
smithco

Olası bir gerekçe, her geliştirme projesinden sonra manuel yeniden test etme çabasını ve maliyetini azaltmaktır. Son projenin sonunda manuel test yaklaşık 2 ay sürdü. Daha otomatik testlerin uygulanması bu çabayı 1-2 haftaya düşürürse buna değebilir.
Anthony

5
LEGACY KODU İÇİN BU PERSONEL İYİDİR!
İş

Bunun oldukça tutarlı ve yapılandırılmış olduğuna katılıyorum. Temel amacım değişimin yan etkilerini azaltmak. Her projeden sonra (ve sırasında) tüm uygulamayı manuel olarak test etmek için gereken çaba çok büyüktür. Selenium'u istemci tarafı üzerinden test etmek için kullanmayı düşünmüştüm - hızlı bir şekilde veritabanını geri döndürme konusunda öneriler almak için ServerFault ( serverfault.com/questions/236546/… ) ile ilgili bir sorum var . Otomatik kabul testinin büyük bir yeniden yazma yapmak zorunda kalmadan faydalarının çoğunu alacağını düşünüyorum.
Anthony

Yanıtlar:


16

Birincil mesajlardan birinin "Çocuk İzci Kuralı" olduğu iki kitaptan bahsediyorsunuz, yani dokunduğunuzda kodu temizleyin. Çalışan bir sisteminiz varsa, büyük bir yeniden yazma karşı üretken olur. Bunun yerine, yeni işlevler ekledikçe kodu olduğu gibi geliştirdiğinizden emin olun.

  • Değiştirmeniz gereken mevcut kodu kapsayacak birim testleri yazın.
  • Bu kodu yeniden kodlayın, böylece değişiklik için daha esnek olur (testlerinizin hala geçtiğinden emin olun).
  • Yeni / gözden geçirilmiş işlevsellik için test yazma
  • Yeni testleri geçmek için kod yazın
  • Gerektiğinde refactor.

Daha fazla dalmak için Feathers, uygulamayı dikişlerinde test etmekten bahsediyor: birimlerin bağlandığı mantıksal noktalar. Bağımlı nesnenin etrafına testler yazabilmeniz için bağımlılık için bir saplama veya alay oluşturmak için bir dikişten yararlanabilirsiniz. AddressBO'yu örnek olarak ele alalım

public class AddressBO
{
    public TransferObject GetAddress(string addressID)
    {
        if (StringUtils.IsNull(addressID))
        {
            throw new ValidationException("Address ID must be entered");
        }

        AddressDAO addressDAO = new AddressDAO();
        return addressDAO.GetAddress(addressID);
    }
}

AddressBO ve AddressDAO arasında belirgin bir dikiş var. AddressDAO için bir arayüz oluşturalım ve bağımlılığın AddressBO'ya enjekte edilmesine izin verelim.

public interface IAddressDAO
{
  TransferObject GetAddress(addressID);
  //add other interface methods here.
}

public class AddressDAO:GenericDAO, IAddressDAO
{
  public TransferObject GetAddress(string addressID)
  {
    ///implementation goes here
  }
}

Şimdi enjeksiyona izin vermek için AddressBO'nuzu doktor

public class AddressBO
{
    private IAddressDAO _addressDAO;
    public AddressBO()
    {
      _addressDAO = new AddressDAO();
    }

    public AddressBO(IAddressDAO addressDAO)
    {
      _addressDAO = addressDAO;
    }

    public TransferObject GetAddress(string addressID)
    {
        if (StringUtils.IsNull(addressID))
        {
            throw new ValidationException("Address ID must be entered");
        }
        //call the injected AddressDAO
        return _addressDAO.GetAddress(addressID);
    }
}

Burada "zavallı adamın bağımlılık enjeksiyonu" kullanıyoruz. Tek amacımız dikişi kırmak ve AddressBO'yu test etmemize izin vermektir. Şimdi birim testlerimizde sahte bir IAddressDAO yapabilir ve iki nesne arasındaki etkileşimleri doğrulayabiliriz.


1
Kabul ediyorum - bu stratejiyi okuduğumda beğendim. Gerçekten değer katmadan kodu temizlemek için aylar harcayabiliriz. Yeni bir özellik eklerken değiştirmemiz gerekenleri temizlemeye odaklanırsak, her iki dünyanın da en iyisini elde ederiz.
Anthony

Tek zorluk mevcut kod için birim testleri yazmak. Birim testlerini daha güvenli bir şekilde yeniden düzenleyebilir ve ekleyebiliriz.
Anthony

1
Evet, yapabileceğiniz en iyi kodun ne yaptığını doğrulayan testler yazmaktır. Doğru davranışı doğrulayan testler oluşturmayı deneyebilirsiniz ... ancak testlerin kapsamadığı diğer işlevleri bozma riskiyle karşı karşıya kalırsınız.
Michael Brown

Tüyleri "bir dikiş bul" u uygulamaya koyduğum için gördüğüm en iyi açıklama bu. Birisi prosedürel olarak OO'dan daha usta olduğu için, AddressBO ve AddressDAO arasında açık bir dikiş var bana göre belli değildi, ama bu örnek gerçekten yardımcı oluyor.
SeraM

5

Doğru hatırlıyorsam Eski Kod ile Etkili Çalışmak , yeniden yazma konusunda tam olarak yeni kodun eskisinden daha iyi olacağını garanti etmez (işlevsellik / kusurlar açısından). Bu kitaptaki yeniden düzenleme işlemleri, hataları düzeltmek / yeni özellikler eklemek içindir.

Ben tavsiye ederim başka bir kitap temelde yeniden yazma üzerinde de yapmak değil diyor .NET Brownfield Uygulama Geliştirme olduğunu. Yeni özellikler eklediğinizde veya hataları düzelttiğinizde sabit, yinelemeli değişiklikler yapmadan bahsediyor. Maliyet ve fayda hususlarını ele alır ve bir seferde çok fazla ısırmaya çalışmak konusunda uyarır. İken Çalışma Etkili Legacy Kanunu ile mikro / kod düzeyinde gözden geçirmeniz, nasıl çoğunlukla konuşur Brownfield Uygulama Geliştirme .NET , çoğunlukla üst düzey hususları kapsar zaman (bazı kod düzeyinde malzeme ile birlikte çok) yeniden faktoring.

Brownfield kitabı, kodun hangi alanının size en fazla soruna neden olduğunu ve oraya odaklanmanızı da önerir. Çok fazla bakıma ihtiyaç duymayan diğer alanlar değişmeye değmeyebilir.



Kitap tavsiyesi için teşekkürler - Bir göz atacağım. Genel bakıştan, özellikle C, C ++ ve Java'ya odaklanmış görünen iki kitaptan daha fazla .NET'e odaklanacağız.
Anthony

4

Böyle eski bir uygulama için, birim testleri yerine (otomatik) yüksek seviye entegrasyon testleriyle başlayarak çok daha uygun maliyetlidir. Ardından güvenlik ağı olarak entegrasyon testleri ile, küçük adımlarla üstlenmeden başlayabilirsiniz eğer üstlenmeden maliyeti geri uzun vadede kendini öder eğer yani uygundur. Diğerlerinin de belirttiği gibi, bu açık değildir.

Benzer bir soruya daha önceki cevabım da bakınız ; Umarım faydalı bulursunuz.


Başlamak için daha yüksek seviye testlere yöneliyorum. Her test yürütme sonra veritabanını geri almak için en iyi yolu çalışmak için DBA ile çalışıyorum.
Anthony

1

Koşu kodunu atmak ve yeniden yazmak için çok dikkatli olun ( Asla yapmamanız gereken şeyler ). Tabii çirkin olabilir, ama eğer çalışırsa bırakın. Joel'in blog yayınına bakın, 10 yaşından büyük, ama hala doğru hedef.


Orada Joel ile aynı fikirde değilim. Söylediği şey o zaman alakalı olabilir, ancak şimdi Mozilla Firefox olarak adlandırılan yeniden yazma değil mi?
CashCow

1
Evet, ama bu süreçte netscape'i iş dışı bıraktı! Onun baştan başlamak asla doğru bir seçim olduğunu söylemiyor ama onun hakkında çok dikkatli olmak bir şey. Ve OO kodu her zaman prosedür kodundan daha iyi değildir.
Zachary K

1

Mike 'çocuk izci kuralı' muhtemelen burada en iyi belirtildiği gibi, kod çalışır ve sürekli bir hata raporları kaynağı değilse, orada oturmak ve zaman içinde yavaş yavaş geliştirmek için tercih ederim.

Geliştirme projeniz sırasında yeni şeyler yapmanıza izin verin. Örneğin, yeni özellikler için ORM kullanın ve mevcut veri katmanı desenini atlayın. Mevcut koda dokunması gereken geliştirmelerle karşılaştığınızda, ilgili kodun bir kısmını yeni şeyler yapmanın yeni yoluna taşıyabilirsiniz. Bir cepheyi veya bazı adaptörleri yerlerde kullanmak, belki de katman başına bile eski kodu izole etmenize yardımcı olabilir. Bu, eski kodu zaman içinde aç bırakmanıza yardımcı olabilir.

Benzer şekilde bu, birim testleri eklemenize yardımcı olabilir, yaptığınız yeni kodla başlayabilir ve yeni geliştirmeler için dokunmanız gereken eski kod için yavaş yavaş bazı testler ekleyebilirsiniz.


1

Bunlar iyi kitaplar. Kodunuzu bu şekilde yeniden yazmaya başlayacaksanız, kodu yeniden yazarken sabit kalmasına yardımcı olmak için kodları birim testleri ile de örtmeye başlamanın önemli olduğunu düşünüyorum.

Küçük adımlarla yapılması ve bu tür bir kodun değiştirilmesi tüm sistemin kolayca dengesizleşmesini sağlayabilir.

Aktif olarak üzerinde çalışmadığınız herhangi bir kodu değiştirmezdim. Bunu yalnızca aktif olarak geliştirdiğiniz veya düzelttiğiniz kodda yapın. Bir şey amacına hizmet ediyorsa ancak yıllardır değiştirilmediyse bırakın. Daha iyi bir yol biliyor olsanız bile bu bir şey yapıyor.

Günün sonunda şirketin üretkenliğe ihtiyacı var. Daha iyi kod üretkenliği artırırken yeniden kodlama daha iyi yazılabileceğinden, muhtemelen ürününüze değer katmanın en iyi yolu değildir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.